#include "stdafx.h";
/*
 * Copyright (C) 1998  Mark Baysinger (mbaysing@ucsd.edu)
 * Copyright (C) 1998,1999,2000,2001  Ross Combs (rocombs@cs.nmsu.edu)
 * Copyright (C) 1999,2000  Rob Crittenden (rcrit@greyoak.com)
 * Copyright (C) 2000,2001  Marco Ziech (mmz@gmx.net)
 * Copyright (C) 2003 Dizzy (dizzy@roedu.net)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#define VERSIONCHECK_INTERNAL_ACCESS
#include "common/setup_before.h"
#include <stdio.h>
#include <ctype.h>
// amadeo
#ifdef WIN32_GUI
#include <win32/winmain.h>
#endif
// NonReal:
#include <sys/stat.h>
#ifdef HAVE_STDDEF_H
# include <stddef.h>
#else
# ifndef NULL
#  define NULL ((void *)0)
# endif
#endif
#ifdef STDC_HEADERS		/* FIXME: remove ? */
# include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
#  include <strings.h>
# endif
# ifdef HAVE_MEMORY_H
#  include <memory.h>
# endif
#endif
#ifdef WIN32
#include "compat/socket.h"
#endif
#include "compat/strcasecmp.h"
#include "compat/strncasecmp.h"
#include "compat/strchr.h"
#include "compat/strdup.h"
#include "common/packet.h"
#include "common/bnet_protocol.h"
#include "common/tag.h"
#include "message.h"
#include "common/eventlog.h"
#include "command.h"
#include "team.h"
#include "account.h"
#include "account_wrap.h"
#include "realm.h"
#include "connection.h"
#include "channel.h"
#include "game.h"
#include "common/queue.h"
#include "tick.h"
#include "file.h"
#include "prefs.h"
#include "common/util.h"
#include "common/bnethash.h"
#include "common/bnethashconv.h"
#include "common/bn_type.h"
#include "common/field_sizes.h"
#include "ladder.h"
#include "adbanner.h"
#include "common/list.h"
#include "common/bnettime.h"
#include "common/addr.h"
#include "game_conv.h"
#include "autoupdate.h"
#include "character.h"
#include "versioncheck.h"
#include "anongame.h"
#include "handle_anongame.h"
#include "common/proginfo.h"
#include "clan.h"
#include "handle_bnet.h"
#include "handlers.h"
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#include "compat/netinet_in.h"
#include "watch.h"
#include "anongame_infos.h"
#include "news.h"		//by Spider
#include "friends.h"
#include "server.h"
#include "compat/uint.h"
#include "common/trans.h"
#include "common/xalloc.h"
#include "common/lstr.h"
#include "common/setup_after.h"

extern int last_news;
extern int first_news;

/* handlers prototypes */
static int _client_unknown_1b(t_connection * c, t_packet const *const packet);
static int _client_compinfo1(t_connection * c, t_packet const *const packet);
static int _client_compinfo2(t_connection * c, t_packet const *const packet);
static int _client_countryinfo1(t_connection * c, t_packet const *const packet);
static int _client_countryinfo109(t_connection * c, t_packet const *const packet);
static int _client_unknown2b(t_connection * c, t_packet const *const packet);
static int _client_progident(t_connection * c, t_packet const *const packet);
static int _client_createaccountw3(t_connection * c, t_packet const *const packet);
static int _client_createacctreq1(t_connection * c, t_packet const *const packet);
static int _client_createacctreq2(t_connection * c, t_packet const *const packet);
static int _client_changepassreq(t_connection * c, t_packet const *const packet);
static int _client_echoreply(t_connection * c, t_packet const *const packet);
static int _client_authreq1(t_connection * c, t_packet const *const packet);
static int _client_authreq109(t_connection * c, t_packet const *const packet);
static int _client_regsnoopreply(t_connection * c, t_packet const *const packet);
static int _client_iconreq(t_connection * c, t_packet const *const packet);
static int _client_cdkey(t_connection * c, t_packet const *const packet);
static int _client_cdkey2(t_connection * c, t_packet const *const packet);
static int _client_cdkey3(t_connection * c, t_packet const *const packet);
static int _client_udpok(t_connection * c, t_packet const *const packet);
static int _client_fileinforeq(t_connection * c, t_packet const *const packet);
static int _client_statsreq(t_connection * c, t_packet const *const packet);
static int _client_loginreq1(t_connection * c, t_packet const *const packet);
static int _client_loginreq2(t_connection * c, t_packet const *const packet);
static int _client_loginreqw3(t_connection * c, t_packet const *const packet);
static int _client_pingreq(t_connection * c, t_packet const *const packet);
static int _client_logonproofreq(t_connection * c, t_packet const *const packet);
static int _client_changegameport(t_connection * c, t_packet const *const packet);
static int _client_friendslistreq(t_connection * c, t_packet const *const packet);
static int _client_friendinforeq(t_connection * c, t_packet const *const packet);
static int _client_atfriendscreen(t_connection * c, t_packet const *const packet);
static int _client_atinvitefriend(t_connection * c, t_packet const *const packet);
static int _client_atacceptinvite(t_connection * c, t_packet const *const packet);
static int _client_atacceptdeclineinvite(t_connection * c, t_packet const *const packet);
static int _client_motdw3(t_connection * c, t_packet const *const packet);
static int _client_realmlistreq(t_connection * c, t_packet const *const packet);
static int _client_realmlistreq110(t_connection * c, t_packet const *const packet);
static int _client_profilereq(t_connection * c, t_packet const *const packet);
static int _client_realmjoinreq109(t_connection * c, t_packet const *const packet);
static int _client_unknown39(t_connection * c, t_packet const *const packet);
static int _client_charlistreq(t_connection * c, t_packet const *const packet);
static int _client_adreq(t_connection * c, t_packet const *const packet);
static int _client_adack(t_connection * c, t_packet const *const packet);
static int _client_adclick(t_connection * c, t_packet const *const packet);
static int _client_adclick2(t_connection * c, t_packet const *const packet);
static int _client_statsupdate(t_connection * c, t_packet const *const packet);
static int _client_playerinforeq(t_connection * c, t_packet const *const packet);
static int _client_progident2(t_connection * c, t_packet const *const packet);
static int _client_joinchannel(t_connection * c, t_packet const *const packet);
static int _client_message(t_connection * c, t_packet const *const packet);
static int _client_gamelistreq(t_connection * c, t_packet const *const packet);
static int _client_joingame(t_connection * c, t_packet const *const packet);
static int _client_startgame1(t_connection * c, t_packet const *const packet);
static int _client_startgame3(t_connection * c, t_packet const *const packet);
static int _client_startgame4(t_connection * c, t_packet const *const packet);
static int _client_closegame(t_connection * c, t_packet const *const packet);
static int _client_gamereport(t_connection * c, t_packet const *const packet);
static int _client_leavechannel(t_connection * c, t_packet const *const packet);
static int _client_ladderreq(t_connection * c, t_packet const *const packet);
static int _client_laddersearchreq(t_connection * c, t_packet const *const packet);
static int _client_mapauthreq1(t_connection * c, t_packet const *const packet);
static int _client_mapauthreq2(t_connection * c, t_packet const *const packet);
static int _client_changeclient(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clanmemberlistreq(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clan_motdreq(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clan_motdchg(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clan_createreq(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clan_createinvitereq(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clan_createinvitereply(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clan_disbandreq(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clanmember_rankupdatereq(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clanmember_removereq(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clan_membernewchiefreq(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clan_invitereq(t_connection * c, t_packet const *const packet);
static int _client_w3xp_clan_invitereply(t_connection * c, t_packet const *const packet);
static int _client_crashdump(t_connection * c, t_packet const *const packet);
static int _client_setemailreply(t_connection * c, t_packet const *const packet);
static int _client_changeemailreq(t_connection * c, t_packet const *const packet);
static int _client_getpasswordreq(t_connection * c, t_packet const *const packet);
static int _client_claninforeq(t_connection * c, t_packet const *const packet);

/* connection state connected handler table */
static const t_htable_row bnet_htable_con[] = {
            {CLIENT_UNKNOWN_1B, _client_unknown_1b},
            {CLIENT_COMPINFO1, _client_compinfo1},
            {CLIENT_COMPINFO2, _client_compinfo2},
            {CLIENT_COUNTRYINFO1, _client_countryinfo1},
            {CLIENT_COUNTRYINFO_109, _client_countryinfo109},
            {CLIENT_UNKNOWN_2B, _client_unknown2b},
            {CLIENT_PROGIDENT, _client_progident},
            {CLIENT_CLOSEGAME, NULL},
            {CLIENT_CREATEACCOUNT_W3, _client_createaccountw3},
            {CLIENT_CREATEACCTREQ1, _client_createacctreq1},
            {CLIENT_CREATEACCTREQ2, _client_createacctreq2},
            {CLIENT_CHANGEPASSREQ, _client_changepassreq},
            {CLIENT_ECHOREPLY, _client_echoreply},
            {CLIENT_AUTHREQ1, _client_authreq1},
            {CLIENT_AUTHREQ_109, _client_authreq109},
            {CLIENT_REGSNOOPREPLY, _client_regsnoopreply},
            {CLIENT_ICONREQ, _client_iconreq},
            {CLIENT_CDKEY, _client_cdkey},
            {CLIENT_CDKEY2, _client_cdkey2},
            {CLIENT_CDKEY3, _client_cdkey3},
            {CLIENT_UDPOK, _client_udpok},
            {CLIENT_FILEINFOREQ, _client_fileinforeq},
            {CLIENT_STATSREQ, _client_statsreq},
            {CLIENT_PINGREQ, _client_pingreq},
            {CLIENT_LOGINREQ1, _client_loginreq1},
            {CLIENT_LOGINREQ2, _client_loginreq2},
            {CLIENT_LOGINREQ_W3, _client_loginreqw3},
            {CLIENT_LOGONPROOFREQ, _client_logonproofreq},
            /* After this packet we know to translate the packets to the normal IDs */
            {CLIENT_CHANGECLIENT, _client_changeclient},
            {CLIENT_GETPASSWORDREQ, _client_getpasswordreq},
            {CLIENT_CHANGEEMAILREQ, _client_changeemailreq},
            {CLIENT_CRASHDUMP, _client_crashdump},
            {-1, NULL}
        };

/* connection state loggedin handlers */
static const t_htable_row bnet_htable_log[] = {
            {CLIENT_CHANGEGAMEPORT, _client_changegameport},
            {CLIENT_FRIENDSLISTREQ, _client_friendslistreq},
            {CLIENT_FRIENDINFOREQ, _client_friendinforeq},
            {CLIENT_ARRANGEDTEAM_FRIENDSCREEN, _client_atfriendscreen},
            {CLIENT_ARRANGEDTEAM_INVITE_FRIEND, _client_atinvitefriend},
            {CLIENT_ARRANGEDTEAM_ACCEPT_INVITE, _client_atacceptinvite},
            {CLIENT_ARRANGEDTEAM_ACCEPT_DECLINE_INVITE, _client_atacceptdeclineinvite},
            /* anongame packet (44ff) handled in handle_anongame.c */
            {CLIENT_FINDANONGAME, handle_anongame_packet},
            {CLIENT_FILEINFOREQ, _client_fileinforeq},
            {CLIENT_MOTD_W3, _client_motdw3},
            {CLIENT_REALMLISTREQ, _client_realmlistreq},
            {CLIENT_REALMLISTREQ_110, _client_realmlistreq110},
            {CLIENT_PROFILEREQ, _client_profilereq},
            {CLIENT_REALMJOINREQ_109, _client_realmjoinreq109},
            {CLIENT_UNKNOWN_37, _client_charlistreq},
            {CLIENT_UNKNOWN_39, _client_unknown39},
            {CLIENT_ECHOREPLY, _client_echoreply},
            {CLIENT_PINGREQ, _client_pingreq},
            {CLIENT_ADREQ, _client_adreq},
            {CLIENT_ADACK, _client_adack},
            {CLIENT_ADCLICK, _client_adclick},
            {CLIENT_ADCLICK2, _client_adclick2},
            {CLIENT_STATSREQ, _client_statsreq},
            {CLIENT_STATSUPDATE, _client_statsupdate},
            {CLIENT_PLAYERINFOREQ, _client_playerinforeq},
            {CLIENT_PROGIDENT2, _client_progident2},
            {CLIENT_JOINCHANNEL, _client_joinchannel},
            {CLIENT_MESSAGE, _client_message},
            {CLIENT_GAMELISTREQ, _client_gamelistreq},
            {CLIENT_JOIN_GAME, _client_joingame},
            {CLIENT_STARTGAME1, _client_startgame1},
            {CLIENT_STARTGAME3, _client_startgame3},
            {CLIENT_STARTGAME4, _client_startgame4},
            {CLIENT_CLOSEGAME, _client_closegame},
            {CLIENT_CLOSEGAME2, _client_closegame},
            {CLIENT_GAME_REPORT, _client_gamereport},
            {CLIENT_LEAVECHANNEL, _client_leavechannel},
            {CLIENT_LADDERREQ, _client_ladderreq},
            {CLIENT_LADDERSEARCHREQ, _client_laddersearchreq},
            {CLIENT_MAPAUTHREQ1, _client_mapauthreq1},
            {CLIENT_MAPAUTHREQ2, _client_mapauthreq2},
            {CLIENT_W3XP_CLAN_DISBANDREQ, _client_w3xp_clan_disbandreq},
            {CLIENT_W3XP_CLANMEMBERLIST_REQ, _client_w3xp_clanmemberlistreq},
            {CLIENT_W3XP_CLAN_MOTDCHG, _client_w3xp_clan_motdchg},
            {CLIENT_W3XP_CLAN_MOTDREQ, _client_w3xp_clan_motdreq},
            {CLIENT_W3XP_CLAN_CREATEREQ, _client_w3xp_clan_createreq},
            {CLIENT_W3XP_CLAN_CREATEINVITEREQ, _client_w3xp_clan_createinvitereq},
            {CLIENT_W3XP_CLAN_CREATEINVITEREPLY, _client_w3xp_clan_createinvitereply},
            {CLIENT_W3XP_CLANMEMBER_RANKUPDATE_REQ, _client_w3xp_clanmember_rankupdatereq},
            {CLIENT_W3XP_CLANMEMBER_REMOVE_REQ, _client_w3xp_clanmember_removereq},
            {CLIENT_W3XP_CLAN_MEMBERNEWCHIEFREQ, _client_w3xp_clan_membernewchiefreq},
            {CLIENT_W3XP_CLAN_INVITEREQ, _client_w3xp_clan_invitereq},
            {CLIENT_W3XP_CLAN_INVITEREPLY, _client_w3xp_clan_invitereply},
            {CLIENT_CRASHDUMP, _client_crashdump},
            {CLIENT_SETEMAILREPLY, _client_setemailreply},
            {CLIENT_CLANINFOREQ, _client_claninforeq},
            {CLIENT_NULL, NULL},
            {-1, NULL}
        };

/* main handler function */
static int handle(const t_htable_row * htable, int type, t_connection * c, t_packet const *const packet);

extern int handle_bnet_packet(t_connection * c, t_packet const *const packet)
{
    if (!c) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got NULL connection", conn_get_socket(c));
        return -1;
    }
    if (!packet) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got NULL packet", conn_get_socket(c));
        return -1;
    }
    if (packet_get_class(packet) != packet_class_bnet) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad packet (class %d)", conn_get_socket(c), (int) packet_get_class(packet));
        return -1;
    }

    switch (conn_get_state(c)) {
    case conn_state_connected:
        switch (handle(bnet_htable_con, packet_get_type(packet), c, packet)) {
        case 1:
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] unknown (unlogged in) bnet packet type 0x%04x, len %u", conn_get_socket(c), packet_get_type(packet), packet_get_size(packet));
            break;
        case -1:
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] (unlogged in) got error handling packet type 0x%04x, len %u", conn_get_socket(c), packet_get_type(packet), packet_get_size(packet));
            break;
        };
        break;

    case conn_state_loggedin:
        switch (handle(bnet_htable_log, packet_get_type(packet), c, packet)) {
        case 1:
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] unknown (logged in) bnet packet type 0x%04x, len %u", conn_get_socket(c), packet_get_type(packet), packet_get_size(packet));
            break;
        case -1:
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] (logged in) got error handling packet type 0x%04x, len %u", conn_get_socket(c), packet_get_type(packet), packet_get_size(packet));
            break;
        };
        break;

    case conn_state_untrusted:
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] unknown (untrusted) bnet packet type 0x%04x, len %u", conn_get_socket(c), packet_get_type(packet), packet_get_size(packet));
        break;

    default:
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] invalid login state %d", conn_get_socket(c), conn_get_state(c));
    };

    return 0;
}

static int handle(const t_htable_row * htable, int type, t_connection * c, t_packet const *const packet)
{
    t_htable_row const *p;
    int res = 1;

    for (p = htable; p->type != -1; p++)
        if (p->type == type) {
            res = 0;
            if (p->handler != NULL)
                res = p->handler(c, packet);
            if (res != 2)
                break;		/* return 2 means we want to continue parsing */
        }

    return res;
}

/* checks if a clienttag is in the allowed_clients list
 * @ctag : clienttag integer to check
 * if it's allowed returns 0
 * if it's not allowed returns -1
 */
static int _check_allowed_client(t_clienttag ctag)
{
    if(ctag==CLIENTTAG_BROODWARS_UINT)return 0;
    return -1;
}

/* handlers for bnet packets */
static int _client_unknown_1b(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_unknown_1b)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad UNKNOWN_1B packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_unknown_1b), packet_get_size(packet));
        return -1;
    }

    {
        unsigned int newip;
        unsigned short newport;

        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] UNKNOWN_1B unknown1=0x%04hx", conn_get_socket(c), bn_short_get(packet->u.client_unknown_1b.unknown1));
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] UNKNOWN_1B unknown2=0x%08x", conn_get_socket(c), bn_int_get(packet->u.client_unknown_1b.unknown2));
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] UNKNOWN_1B unknown3=0x%08x", conn_get_socket(c), bn_int_get(packet->u.client_unknown_1b.unknown3));

        newip = bn_int_nget(packet->u.client_unknown_1b.ip);
        newport = bn_short_nget(packet->u.client_unknown_1b.port);

        eventlog(eventlog_level_info, __FUNCTION__, "[%d] UNKNOWN_1B set new UDP address to %s", conn_get_socket(c), addr_num_to_addr_str(newip, newport));
        conn_set_game_addr(c, newip);
        conn_set_game_port(c, newport);
    }
    return 0;
}

static int _client_compinfo1(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_compinfo1)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COMPINFO1 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_compinfo1), packet_get_size(packet));
        return -1;
    }

    {
        char const *host;
        char const *user;

        if (!(host = packet_get_str_const(packet, sizeof(t_client_compinfo1), MAX_WINHOST_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COMPINFO1 packet (missing or too long host)", conn_get_socket(c));
            return -1;
        }
        if (!(user = packet_get_str_const(packet, sizeof(t_client_compinfo1) + strlen(host) + 1, MAX_WINUSER_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COMPINFO1 packet (missing or too long user)", conn_get_socket(c));
            return -1;
        }

        conn_set_host(c, host);
        conn_set_user(c, user);
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_compreply));
        packet_set_type(rpacket, SERVER_COMPREPLY);
        bn_int_set(&rpacket->u.server_compreply.reg_version, SERVER_COMPREPLY_REG_VERSION);
        bn_int_set(&rpacket->u.server_compreply.reg_auth, SERVER_COMPREPLY_REG_AUTH);
        bn_int_set(&rpacket->u.server_compreply.client_id, SERVER_COMPREPLY_CLIENT_ID);
        bn_int_set(&rpacket->u.server_compreply.client_token, SERVER_COMPREPLY_CLIENT_TOKEN);
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }
    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_sessionkey1));
        packet_set_type(rpacket, SERVER_SESSIONKEY1);
        bn_int_set(&rpacket->u.server_sessionkey1.sessionkey, conn_get_sessionkey(c));
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }
    return 0;
}

static int _client_compinfo2(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_compinfo2)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COMPINFO2 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_compinfo2), packet_get_size(packet));
        return -1;
    }

    {
        char const *host;
        char const *user;

        if (!(host = packet_get_str_const(packet, sizeof(t_client_compinfo2), MAX_WINHOST_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COMPINFO2 packet (missing or too long host)", conn_get_socket(c));
            return -1;
        }
        if (!(user = packet_get_str_const(packet, sizeof(t_client_compinfo2) + strlen(host) + 1, MAX_WINUSER_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COMPINFO2 packet (missing or too long user)", conn_get_socket(c));
            return -1;
        }

        conn_set_host(c, host);
        conn_set_user(c, user);
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_compreply));
        packet_set_type(rpacket, SERVER_COMPREPLY);
        bn_int_set(&rpacket->u.server_compreply.reg_version, SERVER_COMPREPLY_REG_VERSION);
        bn_int_set(&rpacket->u.server_compreply.reg_auth, SERVER_COMPREPLY_REG_AUTH);
        bn_int_set(&rpacket->u.server_compreply.client_id, SERVER_COMPREPLY_CLIENT_ID);
        bn_int_set(&rpacket->u.server_compreply.client_token, SERVER_COMPREPLY_CLIENT_TOKEN);
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_sessionkey2));
        packet_set_type(rpacket, SERVER_SESSIONKEY2);
        bn_int_set(&rpacket->u.server_sessionkey2.sessionnum, conn_get_sessionnum(c));
        bn_int_set(&rpacket->u.server_sessionkey2.sessionkey, conn_get_sessionkey(c));
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_countryinfo1(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_countryinfo1)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COUNTRYINFO1 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_countryinfo1), packet_get_size(packet));
        return -1;
    }
    {
        char const *langstr;
        char const *countrycode;
        char const *country;
        unsigned int tzbias;

        if (!(langstr = packet_get_str_const(packet, sizeof(t_client_countryinfo1), MAX_LANG_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COUNTRYINFO1 packet (missing or too long langstr)", conn_get_socket(c));
            return -1;
        }

        if (!(countrycode = packet_get_str_const(packet, sizeof(t_client_countryinfo1) + strlen(langstr) + 1, MAX_COUNTRYCODE_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COUNTRYINFO1 packet (missing or too long countrycode)", conn_get_socket(c));
            return -1;
        }

        if (!(country = packet_get_str_const(packet, sizeof(t_client_countryinfo1) + strlen(langstr) + 1 + strlen(countrycode) + 1, MAX_COUNTRY_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COUNTRYINFO1 packet (missing or too long country)", conn_get_socket(c));
            return -1;
        }

        if (!(packet_get_str_const(packet, sizeof(t_client_countryinfo1) + strlen(langstr) + 1 + strlen(countrycode) + 1 + strlen(country) + 1, MAX_COUNTRYNAME_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COUNTRYINFO1 packet (missing or too long countryname)", conn_get_socket(c));
            return -1;
        }

        tzbias = bn_int_get(packet->u.client_countryinfo1.bias);
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] COUNTRYINFO1 packet from tzbias=0x%04x(%+d) langstr=%s countrycode=%s country=%s", tzbias, uint32_to_int(tzbias), conn_get_socket(c), langstr, countrycode, country);
        conn_set_country(c, country);
        conn_set_tzbias(c, uint32_to_int(tzbias));
    }
    return 0;
}

static int _client_countryinfo109(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_countryinfo_109)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COUNTRYINFO_109 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_countryinfo_109), packet_get_size(packet));
        return -1;
    }

    {
        char const *langstr;
        char const *countryname;
        unsigned int tzbias;
        char archtag_str[5];
        char clienttag_str[5];
        char gamelang_str[5];

        if (!(langstr = packet_get_str_const(packet, sizeof(t_client_countryinfo_109), MAX_LANG_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COUNTRYINFO_109 packet (missing or too long langstr)", conn_get_socket(c));
            return -1;
        }

        if (!(countryname = packet_get_str_const(packet, sizeof(t_client_countryinfo_109) + strlen(langstr) + 1, MAX_COUNTRYNAME_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad COUNTRYINFO_109 packet (missing or too long countryname)", conn_get_socket(c));
            return -1;
        }

        /* check if it's an allowed client type */
        if (_check_allowed_client(bn_int_get(packet->u.client_countryinfo_109.clienttag))) {
            conn_set_state(c, conn_state_destroy);
            return 0;
        }

        tzbias = bn_int_get(packet->u.client_countryinfo_109.bias);

        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] COUNTRYINFO_109 packet tzbias=0x%04x(%+d) lcid=%u langid=%u arch=\"%s\" client=\"%s\" versionid=0x%08x gamelang=\"%s\"", conn_get_socket(c), tzbias, uint32_to_int(tzbias), bn_int_get(packet->u.client_countryinfo_109.lcid), bn_int_get(packet->u.client_countryinfo_109.langid), tag_uint_to_str(archtag_str, bn_int_get(packet->u.client_countryinfo_109.archtag)), tag_uint_to_str(clienttag_str, bn_int_get(packet->u.client_countryinfo_109.clienttag)), bn_int_get(packet->u.client_countryinfo_109.versionid), tag_uint_to_str(gamelang_str, bn_int_get(packet->u.client_countryinfo_109.gamelang)));

        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] COUNTRYINFO_109 packet from \"%s\" \"%s\"", conn_get_socket(c), countryname, langstr);

        conn_set_country(c, langstr);	/* FIXME: This isn't right.  We want USA not ENU (English-US) */
        conn_set_tzbias(c, uint32_to_int(tzbias));
        conn_set_versionid(c, bn_int_get(packet->u.client_countryinfo_109.versionid));
        conn_set_archtag(c, bn_int_get(packet->u.client_countryinfo_109.archtag));
        conn_set_clienttag(c, bn_int_get(packet->u.client_countryinfo_109.clienttag));
        conn_set_gamelang(c, bn_int_get(packet->u.client_countryinfo_109.gamelang));

        /* First, send an ECHO_REQ */

        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_echoreq));
            packet_set_type(rpacket, SERVER_ECHOREQ);
            bn_int_set(&rpacket->u.server_echoreq.ticks, get_ticks());
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }

        if ((rpacket = packet_create(packet_class_bnet))) {
            t_versioncheck *vc;

            eventlog(eventlog_level_debug, __FUNCTION__, "[%d] selecting version check", conn_get_socket(c));
            vc = versioncheck_create(conn_get_archtag(c), conn_get_clienttag(c));
            conn_set_versioncheck(c, vc);
            packet_set_size(rpacket, sizeof(t_server_authreq_109));
            packet_set_type(rpacket, SERVER_AUTHREQ_109);

            if ((conn_get_clienttag(c) == CLIENTTAG_WARCRAFT3_UINT))
                bn_int_set(&rpacket->u.server_authreq_109.logontype, SERVER_AUTHREQ_109_LOGONTYPE_W3);
            else if ((conn_get_clienttag(c) == CLIENTTAG_WAR3XP_UINT))
                bn_int_set(&rpacket->u.server_authreq_109.logontype, SERVER_AUTHREQ_109_LOGONTYPE_W3XP);
            else
                bn_int_set(&rpacket->u.server_authreq_109.logontype, SERVER_AUTHREQ_109_LOGONTYPE);

            bn_int_set(&rpacket->u.server_authreq_109.sessionkey, conn_get_sessionkey(c));
            bn_int_set(&rpacket->u.server_authreq_109.sessionnum, conn_get_sessionnum(c));
            file_to_mod_time(versioncheck_get_mpqfile(vc), &rpacket->u.server_authreq_109.timestamp);
            packet_append_string(rpacket, versioncheck_get_mpqfile(vc));
            packet_append_string(rpacket, versioncheck_get_eqn(vc));
            eventlog(eventlog_level_debug, __FUNCTION__, "[%d] selected \"%s\" \"%s\"", conn_get_socket(c), versioncheck_get_mpqfile(vc), versioncheck_get_eqn(vc));
            if ((conn_get_clienttag(c) == CLIENTTAG_WARCRAFT3_UINT)
                    || (conn_get_clienttag(c) == CLIENTTAG_WAR3XP_UINT)) {
                char padding[128];
                memset(padding, 0, 128);
                packet_append_data(rpacket, padding, 128);
            }
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }
    return 0;
}

static int _client_unknown2b(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_unknown_2b)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad UNKNOWN_2B packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_unknown_2b), packet_get_size(packet));
        return -1;
    }
    return 0;
}

static int _client_progident(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_progident)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad PROGIDENT packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_progident), packet_get_size(packet));
        return -1;
    }

    if (_check_allowed_client(bn_int_get(packet->u.client_progident.clienttag))) {
        conn_set_state(c, conn_state_destroy);
        return 0;
    }

    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] CLIENT_PROGIDENT archtag=0x%08x clienttag=0x%08x versionid=0x%08x unknown1=0x%08x", conn_get_socket(c), bn_int_get(packet->u.client_progident.archtag), bn_int_get(packet->u.client_progident.clienttag), bn_int_get(packet->u.client_progident.versionid), bn_int_get(packet->u.client_progident.unknown1));

    conn_set_archtag(c, bn_int_get(packet->u.client_progident.archtag));
    conn_set_clienttag(c, bn_int_get(packet->u.client_progident.clienttag));

    if (prefs_get_skip_versioncheck()) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] attempting to skip version check by sending early authreply", conn_get_socket(c));
        /* skip over SERVER_AUTHREQ1 and CLIENT_AUTHREQ1 */
        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_authreply1));
            packet_set_type(rpacket, SERVER_AUTHREPLY1);
            if (bn_int_get(packet->u.client_progident.clienttag) == CLIENTTAG_DIABLO2XP_UINT)
                bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_D2XP_MESSAGE_OK);
            else
                bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_OK);
            packet_append_string(rpacket, "");
            packet_append_string(rpacket, "");	/* FIXME: what's the second string for? */
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    } else {
        t_versioncheck *vc;

        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] selecting version check", conn_get_socket(c));
        vc = versioncheck_create(conn_get_archtag(c), conn_get_clienttag(c));
        conn_set_versioncheck(c, vc);
        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_authreq1));
            packet_set_type(rpacket, SERVER_AUTHREQ1);
            file_to_mod_time(versioncheck_get_mpqfile(vc), &rpacket->u.server_authreq1.timestamp);
            packet_append_string(rpacket, versioncheck_get_mpqfile(vc));
            packet_append_string(rpacket, versioncheck_get_eqn(vc));
            eventlog(eventlog_level_debug, __FUNCTION__, "[%d] selected \"%s\" \"%s\"", conn_get_socket(c), versioncheck_get_mpqfile(vc), versioncheck_get_eqn(vc));
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_createaccountw3(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    char const *username;
    char const *plainpass;
    char upass[20];
    char lpass[20];
    t_hash sc_hash;
    unsigned int i;

    if (packet_get_size(packet) < sizeof(t_client_createaccount_w3)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CREATEACCOUNT_W3 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_createaccount_w3), packet_get_size(packet));
        return -1;
    }

    username = packet_get_str_const(packet, sizeof(t_client_createaccount_w3), UNCHECKED_NAME_STR);
    if (!username) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CREATEACCOUNT_W3 (missing or too long username)", conn_get_socket(c));
        return -1;
    }

    plainpass = packet_get_str_const(packet, 4 + 8 * 4, 16);
    if (!plainpass) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CREATEACCOUNT_W3 (missing password)", conn_get_socket(c));
        return -1;
    }

    rpacket = packet_create(packet_class_bnet);
    if (!rpacket)
        return -1;
    packet_set_size(rpacket, sizeof(t_server_createaccount_w3));
    packet_set_type(rpacket, SERVER_CREATEACCOUNT_W3);

    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] new account requested for \"%s\"", conn_get_socket(c), username);

    if (prefs_get_allow_new_accounts() == 0) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] account not created (disabled)", conn_get_socket(c));
        bn_int_set(&rpacket->u.server_createaccount_w3.result, SERVER_CREATEACCOUNT_W3_RESULT_EXIST);
        goto out;
    }

    if (account_check_name(username) < 0) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] account not created (invalid symbols)", conn_get_socket(c));
        bn_int_set(&rpacket->u.server_createaccount_w3.result, SERVER_CREATEACCOUNT_W3_RESULT_INVALID);
        goto out;
    }

    /* convert plaintext password to uppercase */
    strncpy(upass, plainpass, 16);
    upass[16] = 0;
    for (i = 0; i < strlen(upass); i++)
        if (isascii((int) upass[i]) && islower((int) upass[i]))
            upass[i] = toupper((int) upass[i]);

    /* convert plaintext password to lowercase for sc etc. */
    strncpy(lpass, plainpass, 16);
    lpass[16] = 0;
    for (i = 0; i < strlen(lpass); i++)
        if (isascii((int) lpass[i]) && isupper((int) lpass[i]))
            lpass[i] = tolower((int) lpass[i]);


    //set password hash for sc etc.
    bnet_hash(&sc_hash, strlen(lpass), lpass);
    if (!accountlist_create_account(username, hash_get_str(sc_hash)))
        bn_int_set(&rpacket->u.server_createaccount_w3.result, SERVER_CREATEACCOUNT_W3_RESULT_EXIST);
    else {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] account created", conn_get_socket(c));
        bn_int_set(&rpacket->u.server_createaccount_w3.result, SERVER_CREATEACCOUNT_W3_RESULT_OK);
    }

out:
    conn_push_outqueue(c, rpacket);
    packet_del_ref(rpacket);

    return 0;
}

static int _client_createacctreq1(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    char const *username;
    t_hash newpasshash1;

    if (packet_get_size(packet) < sizeof(t_client_createacctreq1)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CREATEACCTREQ1 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_createacctreq1), packet_get_size(packet));
        return -1;
    }

    if (!(username = packet_get_str_const(packet, sizeof(t_client_createacctreq1), UNCHECKED_NAME_STR))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CREATEACCTREQ1 (missing or too long username)", conn_get_socket(c));
        return -1;
    }

    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] new account requested for \"%s\"", conn_get_socket(c), username);

    rpacket = packet_create(packet_class_bnet);
    if (!rpacket)
        return -1;
    packet_set_size(rpacket, sizeof(t_server_createacctreply1));
    packet_set_type(rpacket, SERVER_CREATEACCTREPLY1);

    if (prefs_get_allow_new_accounts() == 0) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] account not created (disabled)", conn_get_socket(c));
        bn_int_set(&rpacket->u.server_createacctreply1.result, SERVER_CREATEACCTREPLY1_RESULT_NO);
        goto out;
    }

    bnhash_to_hash(packet->u.client_createacctreq1.password_hash1, &newpasshash1);
    if (!accountlist_create_account(username, hash_get_str(newpasshash1))) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] account not created (failed)", conn_get_socket(c));
        bn_int_set(&rpacket->u.server_createacctreply1.result, SERVER_CREATEACCTREPLY1_RESULT_NO);
        goto out;
    }

    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] account created", conn_get_socket(c));
    bn_int_set(&rpacket->u.server_createacctreply1.result, SERVER_CREATEACCTREPLY1_RESULT_OK);

out:
    conn_push_outqueue(c, rpacket);
    packet_del_ref(rpacket);

    return 0;
}

static int _client_createacctreq2(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    char const *username;
    t_hash newpasshash1;

    if (packet_get_size(packet) < sizeof(t_client_createacctreq2)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CLIENT_CREATEACCTREQ2 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_createacctreq2), packet_get_size(packet));
        return -1;
    }

    username = packet_get_str_const(packet, sizeof(t_client_createacctreq2), UNCHECKED_NAME_STR);
    if (!username) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CREATEACCTREQ2 (missing or too long username)", conn_get_socket(c));
        return -1;
    }

    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] new account requested for \"%s\"", conn_get_socket(c), username);

    rpacket = packet_create(packet_class_bnet);
    if (!rpacket)
        return -1;
    packet_set_size(rpacket, sizeof(t_server_createacctreply2));
    packet_set_type(rpacket, SERVER_CREATEACCTREPLY2);

    if (prefs_get_allow_new_accounts() == 0) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] account not created (disabled)", conn_get_socket(c));
        bn_int_set(&rpacket->u.server_createacctreply2.result, SERVER_CREATEACCTREPLY2_RESULT_EXIST);
        goto out;
    }

    if (account_check_name(username) < 0) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] account not created (invalid symbols)", conn_get_socket(c));
        bn_int_set(&rpacket->u.server_createaccount_w3.result, SERVER_CREATEACCTREPLY2_RESULT_INVALID);
        goto out;
    }

    bnhash_to_hash(packet->u.client_createacctreq2.password_hash1, &newpasshash1);
    if (!accountlist_create_account(username, hash_get_str(newpasshash1))) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] account not created (failed)", conn_get_socket(c));
        bn_int_set(&rpacket->u.server_createacctreply2.result, SERVER_CREATEACCTREPLY2_RESULT_EXIST);	/* FIXME: return reason for failure */
        goto out;
    }

    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] account created", conn_get_socket(c));
    bn_int_set(&rpacket->u.server_createacctreply2.result, SERVER_CREATEACCTREPLY2_RESULT_OK);

out:
    conn_push_outqueue(c, rpacket);
    packet_del_ref(rpacket);

    return 0;
}

static int _client_changepassreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_changepassreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CHANGEPASSREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_changepassreq), packet_get_size(packet));
        return -1;
    }

    {
        char const *username;
        t_account *account;

        if (!(username = packet_get_str_const(packet, sizeof(t_client_changepassreq), UNCHECKED_NAME_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CHANGEPASSREQ (missing or too long username)", conn_get_socket(c));
            return -1;
        }

        eventlog(eventlog_level_info, __FUNCTION__, "[%d] password change requested for \"%s\"", conn_get_socket(c), username);

        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;
        packet_set_size(rpacket, sizeof(t_server_changepassack));
        packet_set_type(rpacket, SERVER_CHANGEPASSACK);

        /* fail if logged in or no account */
        if (connlist_find_connection_by_accountname(username) || !(account = accountlist_find_account(username))) {
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] password change for \"%s\" refused (no such account)", conn_get_socket(c), username);
            bn_int_set(&rpacket->u.server_changepassack.message, SERVER_CHANGEPASSACK_MESSAGE_FAIL);
        } else if (account_get_auth_changepass(account) == 0) {	/* default to true */
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] password change for \"%s\" refused (no change access)", conn_get_socket(c), username);
            bn_int_set(&rpacket->u.server_changepassack.message, SERVER_CHANGEPASSACK_MESSAGE_FAIL);
        } else if (conn_get_sessionkey(c) != bn_int_get(packet->u.client_changepassreq.sessionkey)) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] password change for \"%s\" refused (expected session key 0x%08x, got 0x%08x)", conn_get_socket(c), username, conn_get_sessionkey(c), bn_int_get(packet->u.client_changepassreq.sessionkey));
            bn_int_set(&rpacket->u.server_changepassack.message, SERVER_CHANGEPASSACK_MESSAGE_FAIL);
        } else {
            struct {
                bn_int ticks;
                bn_int sessionkey;
                bn_int passhash1[5];
            } temp;
            char const *oldstrhash1;
            t_hash oldpasshash1;
            t_hash oldpasshash2;
            t_hash trypasshash2;
            t_hash newpasshash1;


            if ((oldstrhash1 = account_get_pass(account))) {
                bn_int_set(&temp.ticks, bn_int_get(packet->u.client_changepassreq.ticks));
                bn_int_set(&temp.sessionkey, bn_int_get(packet->u.client_changepassreq.sessionkey));
                if (hash_set_str(&oldpasshash1, oldstrhash1) < 0) {
                    bnhash_to_hash(packet->u.client_changepassreq.newpassword_hash1, &newpasshash1);
                    account_set_pass(account, hash_get_str(newpasshash1));
                    eventlog(eventlog_level_info, __FUNCTION__, "[%d] password change for \"%s\" successful (bad previous password)", conn_get_socket(c), account_get_name(account));
                    bn_int_set(&rpacket->u.server_changepassack.message, SERVER_CHANGEPASSACK_MESSAGE_SUCCESS);
                } else {
                    hash_to_bnhash((t_hash const *) &oldpasshash1, temp.passhash1);	/* avoid warning */
                    bnet_hash(&oldpasshash2, sizeof(temp), &temp);	/* do the double hash */
                    bnhash_to_hash(packet->u.client_changepassreq.oldpassword_hash2, &trypasshash2);

                    if (hash_eq(trypasshash2, oldpasshash2) == 1) {
                        bnhash_to_hash(packet->u.client_changepassreq.newpassword_hash1, &newpasshash1);
                        account_set_pass(account, hash_get_str(newpasshash1));
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] password change for \"%s\" successful (previous password)", conn_get_socket(c), account_get_name(account));
                        bn_int_set(&rpacket->u.server_changepassack.message, SERVER_CHANGEPASSACK_MESSAGE_SUCCESS);
                    } else {
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] password change for \"%s\" refused (wrong password)", conn_get_socket(c), account_get_name(account));
                        conn_increment_passfail_count(c);
                        bn_int_set(&rpacket->u.server_changepassack.message, SERVER_CHANGEPASSACK_MESSAGE_FAIL);
                    }
                }
            } else {
                bnhash_to_hash(packet->u.client_changepassreq.newpassword_hash1, &newpasshash1);
                account_set_pass(account, hash_get_str(newpasshash1));
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] password change for \"%s\" successful (no previous password)", conn_get_socket(c), account_get_name(account));
                bn_int_set(&rpacket->u.server_changepassack.message, SERVER_CHANGEPASSACK_MESSAGE_SUCCESS);
            }
        }

        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);

    }

    return 0;
}

static int _client_echoreply(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_echoreply)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad ECHOREPLY packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_echoreply), packet_get_size(packet));
        return -1;
    }

    {
        unsigned int now;
        unsigned int then;

        now = get_ticks();
        then = bn_int_get(packet->u.client_echoreply.ticks);
        if (!now || !then || now < then)
            eventlog(eventlog_level_warn, __FUNCTION__, "[%d] bad timing in echo reply: now=%u then=%u", conn_get_socket(c), now, then);
        else
            conn_set_latency(c, now - then);
    }

    return 0;
}

static int _client_authreq1(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_authreq1)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad AUTHREQ1 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_authreq1), packet_get_size(packet));
        return -1;
    }

    {
        char verstr[16];
        char const *exeinfo;
        char const *versiontag;
        int failed;

        failed = 0;
        if (bn_int_get(packet->u.client_authreq1.archtag) != conn_get_archtag(c))
            failed = 1;
        if (bn_int_get(packet->u.client_authreq1.clienttag) != conn_get_clienttag(c))
            failed = 1;

        if (!(exeinfo = packet_get_str_const(packet, sizeof(t_client_authreq1), MAX_EXEINFO_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad AUTHREQ1 (missing or too long exeinfo)", conn_get_socket(c));
            exeinfo = "badexe";
            failed = 1;
        }
        conn_set_versionid(c, bn_int_get(packet->u.client_authreq1.versionid));
        conn_set_checksum(c, bn_int_get(packet->u.client_authreq1.checksum));
        conn_set_gameversion(c, bn_int_get(packet->u.client_authreq1.gameversion));
        strcpy(verstr, vernum_to_verstr(bn_int_get(packet->u.client_authreq1.gameversion)));
        conn_set_clientver(c, verstr);
        conn_set_clientexe(c, exeinfo);

        eventlog(eventlog_level_info, __FUNCTION__, "[%d] CLIENT_AUTHREQ1 archtag=0x%08x clienttag=0x%08x verstr=%s exeinfo=\"%s\" versionid=0x%08lx gameversion=0x%08lx checksum=0x%08lx", conn_get_socket(c), bn_int_get(packet->u.client_authreq1.archtag), bn_int_get(packet->u.client_authreq1.clienttag), verstr, exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c));


        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_authreply1));
            packet_set_type(rpacket, SERVER_AUTHREPLY1);


            if (!conn_get_versioncheck(c) && prefs_get_skip_versioncheck())
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] skip versioncheck enabled and client did not request validation", conn_get_socket(c));
            else
                switch (versioncheck_validate(conn_get_versioncheck(c), conn_get_archtag(c), conn_get_clienttag(c), exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c))) {
                case -1:	/* failed test... client has been modified */
                    if (!prefs_get_allow_bad_version()) {
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] client failed test (marking untrusted)", conn_get_socket(c));
                        failed = 1;
                    } else
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] client failed test, allowing anyway", conn_get_socket(c));
                    break;
                case 0:	/* not listed in table... can't tell if client has been modified */
                    if (!prefs_get_allow_unknown_version()) {
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] unable to test client (marking untrusted)", conn_get_socket(c));
                        failed = 1;
                    } else
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] unable to test client, allowing anyway", conn_get_socket(c));
                    break;
                    /* 1 == test passed... client seems to be ok */
                }

            versiontag = versioncheck_get_versiontag(conn_get_versioncheck(c));

            eventlog(eventlog_level_info, __FUNCTION__, "[%d] client matches versiontag \"%s\"", conn_get_socket(c), versiontag);

            if (failed) {
                conn_set_state(c, conn_state_untrusted);
                if (bn_int_get(packet->u.client_progident.clienttag) == CLIENTTAG_DIABLO2XP_UINT)
                    bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_D2XP_MESSAGE_BADVERSION);
                else
                    bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_BADVERSION);
                packet_append_string(rpacket, "");
            } else {
                char *mpqfilename;

                mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), versiontag);

                /* Only handle updates when there is an update file available. */
                if (mpqfilename != NULL) {
                    eventlog(eventlog_level_info, __FUNCTION__, "[%d] an upgrade for version %s is available \"%s\"", conn_get_socket(c), versioncheck_get_versiontag(conn_get_versioncheck(c)), mpqfilename);
                    if (bn_int_get(packet->u.client_progident.clienttag) == CLIENTTAG_DIABLO2XP_UINT)
                        bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_D2XP_MESSAGE_UPDATE);
                    else
                        bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_UPDATE);
                    packet_append_string(rpacket, mpqfilename);
                } else {
                    eventlog(eventlog_level_info, __FUNCTION__, "[%d] no upgrade for %s is available", conn_get_socket(c), versioncheck_get_versiontag(conn_get_versioncheck(c)));
                    if (bn_int_get(packet->u.client_progident.clienttag) == CLIENTTAG_DIABLO2XP_UINT)
                        bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_D2XP_MESSAGE_OK);
                    else
                        bn_int_set(&rpacket->u.server_authreply1.message, SERVER_AUTHREPLY1_MESSAGE_OK);
                    packet_append_string(rpacket, "");
                }

                if (mpqfilename)
                    xfree((void *) mpqfilename);
            }

            packet_append_string(rpacket, "");	/* FIXME: what's the second string for? */
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_authreq109(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_authreq_109)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad AUTHREQ_109 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_authreq_109), packet_get_size(packet));
        return 0;
    }

    {
        char verstr[16];
        char const *exeinfo;
        char const *versiontag;
        int failed;
        char const *owner;
        unsigned int count;
        unsigned int pos;

        failed = 0;
        count = bn_int_get(packet->u.client_authreq_109.cdkey_number);
        pos = sizeof(t_client_authreq_109) + (count * sizeof(t_cdkey_info));

        if (!(exeinfo = packet_get_str_const(packet, pos, MAX_EXEINFO_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad AUTHREQ_109 (missing or too long exeinfo)", conn_get_socket(c));
            exeinfo = "badexe";
            failed = 1;
        }
        conn_set_clientexe(c, exeinfo);
        pos += strlen(exeinfo) + 1;

        if (!(owner = packet_get_str_const(packet, pos, MAX_OWNER_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad AUTHREQ_109 (missing or too long owner)", conn_get_socket(c));
            owner = "";		/* maybe owner was missing, use empty string */
        }
        conn_set_owner(c, owner);

        conn_set_checksum(c, bn_int_get(packet->u.client_authreq_109.checksum));
        conn_set_gameversion(c, bn_int_get(packet->u.client_authreq_109.gameversion));
        strcpy(verstr, vernum_to_verstr(bn_int_get(packet->u.client_authreq_109.gameversion)));
        conn_set_clientver(c, verstr);
        conn_set_clientexe(c, exeinfo);

        eventlog(eventlog_level_info, __FUNCTION__, "[%d] CLIENT_AUTHREQ_109 ticks=0x%08x, verstr=%s exeinfo=\"%s\" versionid=0x%08lx gameversion=0x%08lx checksum=0x%08lx", conn_get_socket(c), bn_int_get(packet->u.client_authreq_109.ticks), verstr, exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c));

        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_authreply_109));
            packet_set_type(rpacket, SERVER_AUTHREPLY_109);


            if (!conn_get_versioncheck(c) && prefs_get_skip_versioncheck())
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] skip versioncheck enabled and client did not request validation", conn_get_socket(c));
            else
                switch (versioncheck_validate(conn_get_versioncheck(c), conn_get_archtag(c), conn_get_clienttag(c), exeinfo, conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c))) {
                case -1:	/* failed test... client has been modified */
                    if (!prefs_get_allow_bad_version()) {
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] client failed test (closing connection)", conn_get_socket(c));
                        failed = 1;
                    } else
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] client failed test, allowing anyway", conn_get_socket(c));
                    break;
                case 0:	/* not listed in table... can't tell if client has been modified */
                    if (!prefs_get_allow_unknown_version()) {
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] unable to test client (closing connection)", conn_get_socket(c));
                        failed = 1;
                    } else
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] unable to test client, allowing anyway", conn_get_socket(c));
                    break;
                    /* 1 == test passed... client seems to be ok */
                }

            versiontag = versioncheck_get_versiontag(conn_get_versioncheck(c));

            eventlog(eventlog_level_info, __FUNCTION__, "[%d] client matches versiontag \"%s\"", conn_get_socket(c), versiontag);

            if (failed) {
                conn_set_state(c, conn_state_untrusted);
                bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_BADVERSION);
                packet_append_string(rpacket, "");
            } else {
                char *mpqfilename;

                mpqfilename = autoupdate_check(conn_get_archtag(c), conn_get_clienttag(c), conn_get_gamelang(c), versiontag);

                /* Only handle updates when there is an update file available. */
                if (mpqfilename != NULL) {
                    eventlog(eventlog_level_info, __FUNCTION__, "[%d] an upgrade for %s is available \"%s\"", conn_get_socket(c), versiontag, mpqfilename);
                    bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_UPDATE);
                    packet_append_string(rpacket, mpqfilename);
                } else {
                    eventlog(eventlog_level_info, __FUNCTION__, "[%d] no upgrade for %s is available", conn_get_socket(c), versiontag);
                    bn_int_set(&rpacket->u.server_authreply_109.message, SERVER_AUTHREPLY_109_MESSAGE_OK);
                    packet_append_string(rpacket, "");
                }
                if (mpqfilename)
                    xfree((void *) mpqfilename);
            }

            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_regsnoopreply(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_regsnoopreply)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad REGSNOOPREPLY packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_regsnoopreply), packet_get_size(packet));
        return -1;
    }
    return 0;
}

static int _client_iconreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_iconreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad ICONREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_iconreq), packet_get_size(packet));
        return -1;
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_iconreply));
        packet_set_type(rpacket, SERVER_ICONREPLY);
        file_to_mod_time(prefs_get_iconfile(), &rpacket->u.server_iconreply.timestamp);

        /* battle.net sends different file on iconreq for WAR3 and W3XP [Omega] */
        if ((conn_get_clienttag(c) == CLIENTTAG_WARCRAFT3_UINT) || (conn_get_clienttag(c) == CLIENTTAG_WAR3XP_UINT))
            packet_append_string(rpacket, prefs_get_war3_iconfile());
        /* battle.net still sends "icons.bni" to sc/bw clients
         * clients request icons_STAR.bni seperatly */
        /*	else if (strcmp(conn_get_clienttag(c),CLIENTTAG_STARCRAFT)==0)
        	    packet_append_string(rpacket,prefs_get_star_iconfile());
        	else if (strcmp(conn_get_clienttag(c),CLIENTTAG_BROODWARS)==0)
        	    packet_append_string(rpacket,prefs_get_star_iconfile());
         */
        else
            packet_append_string(rpacket, prefs_get_iconfile());

        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_cdkey(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_cdkey)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CDKEY packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_cdkey), packet_get_size(packet));
        return -1;
    }

    {
        char const *cdkey;
        char const *owner;

        if (!(cdkey = packet_get_str_const(packet, sizeof(t_client_cdkey), MAX_CDKEY_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CDKEY packet (missing or too long cdkey)", conn_get_socket(c));
            return -1;
        }
        if (!(owner = packet_get_str_const(packet, sizeof(t_client_cdkey) + strlen(cdkey) + 1, MAX_OWNER_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CDKEY packet (missing or too long owner)", conn_get_socket(c));
            return -1;
        }

        conn_set_cdkey(c, cdkey);
        conn_set_owner(c, owner);

        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_cdkeyreply));
            packet_set_type(rpacket, SERVER_CDKEYREPLY);
            bn_int_set(&rpacket->u.server_cdkeyreply.message, SERVER_CDKEYREPLY_MESSAGE_OK);
            packet_append_string(rpacket, owner);
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }
#if 0				/* Blizzard used this to track down pirates, should only be accepted by old clients */
    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_regsnoopreq));
        packet_set_type(rpacket, SERVER_REGSNOOPREQ);
        bn_int_set(&rpacket->u.server_regsnoopreq.unknown1, SERVER_REGSNOOPREQ_UNKNOWN1);	/* sequence num */
        bn_int_set(&rpacket->u.server_regsnoopreq.hkey, SERVER_REGSNOOPREQ_HKEY_CURRENT_USER);
        packet_append_string(rpacket, SERVER_REGSNOOPREQ_REGKEY);
        packet_append_string(rpacket, SERVER_REGSNOOPREQ_REGVALNAME);
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }
#endif
    return 0;
}

static int _client_cdkey2(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_cdkey2)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CDKEY2 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_cdkey2), packet_get_size(packet));
        return -1;
    }

    {
        char const *owner;

        if (!(owner = packet_get_str_const(packet, sizeof(t_client_cdkey2), MAX_OWNER_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CDKEY2 packet (missing or too long owner)", conn_get_socket(c));
            return -1;
        }

        conn_set_owner(c, owner);

        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_cdkeyreply2));
            packet_set_type(rpacket, SERVER_CDKEYREPLY2);
            bn_int_set(&rpacket->u.server_cdkeyreply2.message, SERVER_CDKEYREPLY2_MESSAGE_OK);
            packet_append_string(rpacket, owner);
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_cdkey3(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_cdkey3)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CDKEY3 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_cdkey2), packet_get_size(packet));
        return -1;
    }

    {
        char const *owner;

        if (!(owner = packet_get_str_const(packet, sizeof(t_client_cdkey3), MAX_OWNER_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CDKEY3 packet (missing or too long owner)", conn_get_socket(c));
            return -1;
        }

        conn_set_owner(c, owner);

        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_cdkeyreply3));
            packet_set_type(rpacket, SERVER_CDKEYREPLY3);
            bn_int_set(&rpacket->u.server_cdkeyreply3.message, SERVER_CDKEYREPLY3_MESSAGE_OK);
            packet_append_string(rpacket, "");	/* FIXME: owner, message, ??? */
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_udpok(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_udpok)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad UDPOK packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_udpok), packet_get_size(packet));
        return -1;
    }
    /* we could check the contents but there really isn't any point */
    conn_set_udpok(c);

    return 0;
}

static int _client_fileinforeq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_fileinforeq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad FILEINFOREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_fileinforeq), packet_get_size(packet));
        return -1;
    }

    {
        char const *tosfile;

        if (!(tosfile = packet_get_str_const(packet, sizeof(t_client_fileinforeq), MAX_FILENAME_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad FILEINFOREQ packet (missing or too long tosfile)", conn_get_socket(c));
            return -1;
        }
        eventlog(eventlog_level_info, __FUNCTION__, "[%d] TOS requested: \"%s\" - type = 0x%02x", conn_get_socket(c), tosfile, bn_int_get(packet->u.client_fileinforeq.type));

        /* TODO: if type is TOSFILE make bnetd to send default tosfile if selected is not found */
        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_fileinforeply));
            packet_set_type(rpacket, SERVER_FILEINFOREPLY);
            bn_int_set(&rpacket->u.server_fileinforeply.type, bn_int_get(packet->u.client_fileinforeq.type));
            bn_int_set(&rpacket->u.server_fileinforeply.unknown2, bn_int_get(packet->u.client_fileinforeq.unknown2));
            /* Note from Sherpya:
             * timestamp -> 0x852b7d00 - 0x01c0e863 b.net send this (bn_int),
             * I suppose is not a long 
             * if bnserver-D2DV is bad diablo 2 crashes 
             * timestamp doesn't work correctly and starcraft 
             * needs name in client locale or displays hostname
             */
            file_to_mod_time(tosfile, &rpacket->u.server_fileinforeply.timestamp);
            packet_append_string(rpacket, tosfile);
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static const char *_attribute_req(t_account * reqacc, t_account * myacc, const char *key)
{
    const char *result = "";
    const char *tval;

    if (!reqacc)
        goto out;
    if (reqacc != myacc && !strncasecmp(key, "BNET", 4))
        goto out;

    tval = account_get_strattr(reqacc, key);
    if (tval)
        result = tval;

out:
    return result;
}

static int _client_statsreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    char const *name;
    char const *key;
    unsigned int name_count;
    unsigned int key_count;
    unsigned int i, j;
    unsigned int name_off;
    unsigned int keys_off;
    unsigned int key_off;
    t_account *reqacc, *myacc;

    if (packet_get_size(packet) < sizeof(t_client_statsreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STATSREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_statsreq), packet_get_size(packet));
        return -1;
    }

    name_count = bn_int_get(packet->u.client_statsreq.name_count);
    key_count = bn_int_get(packet->u.client_statsreq.key_count);

    for (i = 0, name_off = sizeof(t_client_statsreq); i < name_count && (name = packet_get_str_const(packet, name_off, UNCHECKED_NAME_STR)); i++, name_off += strlen(name) + 1);

    if (i < name_count) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STATSREQ packet (only %u names of %u)", conn_get_socket(c), i, name_count);
        return -1;
    }
    keys_off = name_off;

    if (!(rpacket = packet_create(packet_class_bnet)))
        return -1;

    packet_set_size(rpacket, sizeof(t_server_statsreply));
    packet_set_type(rpacket, SERVER_STATSREPLY);
    bn_int_set(&rpacket->u.server_statsreply.name_count, name_count);
    bn_int_set(&rpacket->u.server_statsreply.key_count, key_count);
    bn_int_set(&rpacket->u.server_statsreply.requestid, bn_int_get(packet->u.client_statsreq.requestid));

    myacc = conn_get_account(c);

    for (i = 0, name_off = sizeof(t_client_statsreq); i < name_count && (name = packet_get_str_const(packet, name_off, UNCHECKED_NAME_STR)); i++, name_off += strlen(name) + 1) {
        reqacc = accountlist_find_account(name);
        if (!reqacc)
            reqacc = myacc;

        for (j = 0, key_off = keys_off; j < key_count && (key = packet_get_str_const(packet, key_off, MAX_ATTRKEY_STR)); j++, key_off += strlen(key) + 1) {
            if (*key == '\0')
                continue;
            packet_append_string(rpacket, _attribute_req(reqacc, myacc, key));
        }
    }

    conn_push_outqueue(c, rpacket);
    packet_del_ref(rpacket);

    return 0;
}

static int _client_loginreq1(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_loginreq1)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad LOGINREQ1 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_loginreq1), packet_get_size(packet));
        return -1;
    }

    {
        char const *username;
        t_account *account;

        if (!(username = packet_get_str_const(packet, sizeof(t_client_loginreq1), USER_NAME_MAX))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad LOGINREQ1 (missing or too long username)", conn_get_socket(c));
            return -1;
        }

        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;
        packet_set_size(rpacket, sizeof(t_server_loginreply1));
        packet_set_type(rpacket, SERVER_LOGINREPLY1);

        // too many logins? [added by NonReal]
        if (prefs_get_max_concurrent_logins() > 0) {
            if (prefs_get_max_concurrent_logins() <= connlist_login_get_length()) {
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] login denied, too many concurrent logins. max: %d. current: %d.", conn_get_socket(c), prefs_get_max_concurrent_logins(), connlist_login_get_length());
                bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY1_MESSAGE_FAIL);
                return -1;
            }
        }

        /* fail if no account */
        if (!(account = accountlist_find_account(username))) {
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (no such account)", conn_get_socket(c), username);
            bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY1_MESSAGE_FAIL);
        } else
            /* already logged in */
            if (connlist_find_connection_by_account(account) && prefs_get_kick_old_login() == 0) {
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (already logged in)", conn_get_socket(c), username);
                bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY1_MESSAGE_FAIL);
            } else if (account_get_auth_bnetlogin(account) == 0) {	/* default to true */
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (no bnet access)", conn_get_socket(c), username);
                bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY1_MESSAGE_FAIL);
            } else if (account_get_auth_lock(account) == 1) {	/* default to false */
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (this account is locked)", conn_get_socket(c), username);
                bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY1_MESSAGE_FAIL);
            } else if (conn_get_sessionkey(c) != bn_int_get(packet->u.client_loginreq1.sessionkey)) {
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] login for \"%s\" refused (expected session key 0x%08x, got 0x%08x)", conn_get_socket(c), username, conn_get_sessionkey(c), bn_int_get(packet->u.client_loginreq1.sessionkey));
                bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY1_MESSAGE_FAIL);
            } else {
                struct {
                    bn_int ticks;
                    bn_int sessionkey;
                    bn_int passhash1[5];
                } temp;
                char const *oldstrhash1;
                t_hash oldpasshash1;
                t_hash oldpasshash2;
                t_hash trypasshash2;

                if ((oldstrhash1 = account_get_pass(account))) {
                    bn_int_set(&temp.ticks, bn_int_get(packet->u.client_loginreq1.ticks));
                    bn_int_set(&temp.sessionkey, bn_int_get(packet->u.client_loginreq1.sessionkey));
                    if (hash_set_str(&oldpasshash1, oldstrhash1) < 0) {
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (corrupted passhash1?)", conn_get_socket(c), username);
                        bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY1_MESSAGE_FAIL);
                    } else {
                        hash_to_bnhash((t_hash const *) &oldpasshash1, temp.passhash1);	/* avoid warning */

                        bnet_hash(&oldpasshash2, sizeof(temp), &temp);	/* do the double hash */
                        bnhash_to_hash(packet->u.client_loginreq1.password_hash2, &trypasshash2);

                        if (hash_eq(trypasshash2, oldpasshash2) == 1) {
                            conn_login(c, account, username);
                            eventlog(eventlog_level_info, __FUNCTION__, "[%d] \"%s\" logged in (correct password)", conn_get_socket(c), username);
                            bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY1_MESSAGE_SUCCESS);
#ifdef WIN32_GUI
                            guiOnUpdateUserList();
#endif
                        } else {
                            eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (wrong password)", conn_get_socket(c), username);
                            conn_increment_passfail_count(c);
                            bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY1_MESSAGE_FAIL);
                        }
                    }
                } else {
                    conn_login(c, account, username);
                    eventlog(eventlog_level_info, __FUNCTION__, "[%d] \"%s\" logged in (no password)", conn_get_socket(c), username);
                    bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY1_MESSAGE_SUCCESS);
#ifdef WIN32_GUI
                    guiOnUpdateUserList();
#endif
                }
            }
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

void client_init_email(t_connection * c, t_account * account)
{
    t_packet *packet;
    char const *email;

    if (!c || !account)
        return;
    if (!(email = account_get_email(account))) {
        if ((packet = packet_create(packet_class_bnet))) {
            packet_set_size(packet, sizeof(t_server_setemailreq));
            packet_set_type(packet, SERVER_SETEMAILREQ);
            conn_push_outqueue(c, packet);
            packet_del_ref(packet);
        }
    }
    return;
}

static int _client_loginreq2(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    int success = 0;

    if (packet_get_size(packet) < sizeof(t_client_loginreq2)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad LOGINREQ2 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_loginreq2), packet_get_size(packet));
        return -1;
    }

    {
        char const *username;
        t_account *account;

        if (!(username = packet_get_str_const(packet, sizeof(t_client_loginreq2), USER_NAME_MAX))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad LOGINREQ2 (missing or too long username)", conn_get_socket(c));
            return -1;
        }

        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;
        packet_set_size(rpacket, sizeof(t_server_loginreply2));
        packet_set_type(rpacket, SERVER_LOGINREPLY2);

        // too many logins? [added by NonReal]
        if (prefs_get_max_concurrent_logins() > 0) {
            if (prefs_get_max_concurrent_logins() <= connlist_login_get_length()) {
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] login denied, too many concurrent logins. max: %d. current: %d.", conn_get_socket(c), prefs_get_max_concurrent_logins(), connlist_login_get_length());
                bn_int_set(&rpacket->u.server_loginreply2.message, SERVER_LOGINREPLY2_MESSAGE_BADPASS);
                return -1;
            }
        }

        /* fail if no account */
        if (!(account = accountlist_find_account(username))) {
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (no such account)", conn_get_socket(c), username);
            bn_int_set(&rpacket->u.server_loginreply2.message, SERVER_LOGINREPLY2_MESSAGE_BADPASS);
        }
        /* already logged in */
        else if (connlist_find_connection_by_account(account) && prefs_get_kick_old_login() == 0) {
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (already logged in)", conn_get_socket(c), username);
            bn_int_set(&rpacket->u.server_loginreply2.message, SERVER_LOGINREPLY2_MESSAGE_ALREADY);
        } else if (account_get_auth_bnetlogin(account) == 0) {	/* default to true */
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (no bnet access)", conn_get_socket(c), username);
            bn_int_set(&rpacket->u.server_loginreply2.message, SERVER_LOGINREPLY2_MESSAGE_BADPASS);
        } else if (account_get_auth_lock(account) == 1) {	/* default to false */
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (this account is locked)", conn_get_socket(c), username);
            bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY2_MESSAGE_BADPASS);
        } else if (conn_get_sessionkey(c) != bn_int_get(packet->u.client_loginreq2.sessionkey)) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] login for \"%s\" refused (expected session key 0x%08x, got 0x%08x)", conn_get_socket(c), username, conn_get_sessionkey(c), bn_int_get(packet->u.client_loginreq2.sessionkey));
            bn_int_set(&rpacket->u.server_loginreply2.message, SERVER_LOGINREPLY2_MESSAGE_BADPASS);
        } else {
            struct {
                bn_int ticks;
                bn_int sessionkey;
                bn_int passhash1[5];
            } temp;
            char const *oldstrhash1;
            t_hash oldpasshash1;
            t_hash oldpasshash2;
            t_hash trypasshash2;

            if ((oldstrhash1 = account_get_pass(account))) {
                bn_int_set(&temp.ticks, bn_int_get(packet->u.client_loginreq2.ticks));
                bn_int_set(&temp.sessionkey, bn_int_get(packet->u.client_loginreq2.sessionkey));
                if (hash_set_str(&oldpasshash1, oldstrhash1) < 0) {
                    eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (corrupted passhash1?)", conn_get_socket(c), username);
                    bn_int_set(&rpacket->u.server_loginreply2.message, SERVER_LOGINREPLY2_MESSAGE_BADPASS);
                } else {
                    hash_to_bnhash((t_hash const *) &oldpasshash1, temp.passhash1);	/* avoid warning */

                    bnet_hash(&oldpasshash2, sizeof(temp), &temp);	/* do the double hash */
                    bnhash_to_hash(packet->u.client_loginreq2.password_hash2, &trypasshash2);

                    if (hash_eq(trypasshash2, oldpasshash2) == 1) {
                        conn_login(c, account, username);
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] \"%s\" logged in (correct password)", conn_get_socket(c), username);
                        bn_int_set(&rpacket->u.server_loginreply2.message, SERVER_LOGINREPLY2_MESSAGE_SUCCESS);
                        success = 1;
                    } else {
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (wrong password)", conn_get_socket(c), username);
                        conn_increment_passfail_count(c);
                        bn_int_set(&rpacket->u.server_loginreply2.message, SERVER_LOGINREPLY2_MESSAGE_BADPASS);
                    }
                }
            } else {
                conn_login(c, account, username);
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] \"%s\" logged in (no password)", conn_get_socket(c), username);
                bn_int_set(&rpacket->u.server_loginreply2.message, SERVER_LOGINREPLY2_MESSAGE_SUCCESS);
                success = 1;
            }
        }
        if (success && account) {
#ifdef WIN32_GUI
            guiOnUpdateUserList();
#endif
            client_init_email(c, account);
        }

        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_loginreqw3(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_loginreq_w3)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CLIENT_LOGINREQ_W3 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_loginreq_w3), packet_get_size(packet));
        return -1;
    }

    {
        char const *username;
        t_account *account;
        int i;

        if (!(username = packet_get_str_const(packet, sizeof(t_client_loginreq_w3), USER_NAME_MAX))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CLIENT_LOGINREQ_W3 (missing or too long username)", conn_get_socket(c));
            return -1;
        }

        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;
        packet_set_size(rpacket, sizeof(t_server_loginreply_w3));
        packet_set_type(rpacket, SERVER_LOGINREPLY_W3);

        for (i = 0; i < 16; i++)
            bn_int_set(&rpacket->u.server_loginreply_w3.unknown[i], 0);

        {
            /* too many logins? */
            if (prefs_get_max_concurrent_logins() > 0 && prefs_get_max_concurrent_logins() <= connlist_login_get_length()) {
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] login denied, too many concurrent logins. max: %d. current: %d.", conn_get_socket(c), prefs_get_max_concurrent_logins(), connlist_login_get_length());
                bn_int_set(&rpacket->u.server_loginreply_w3.message, SERVER_LOGINREPLY_W3_MESSAGE_BADACCT);
            } else
                /* fail if no account */
                if (!(account = accountlist_find_account(username))) {
                    eventlog(eventlog_level_info, __FUNCTION__, "[%d] (W3) login for \"%s\" refused (no such account)", conn_get_socket(c), username);
                    bn_int_set(&rpacket->u.server_loginreply_w3.message, SERVER_LOGINREPLY_W3_MESSAGE_BADACCT);
                } else
                    /* already logged in */
                    if (connlist_find_connection_by_account(account) && prefs_get_kick_old_login() == 0) {
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] (W3) login for \"%s\" refused (already logged in)", conn_get_socket(c), username);
                        bn_int_set(&rpacket->u.server_loginreply_w3.message, SERVER_LOGINREPLY_W3_MESSAGE_ALREADY);
                    } else if (account_get_auth_bnetlogin(account) == 0) {	/* default to true */
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] (W3) login for \"%s\" refused (no bnet access)", conn_get_socket(c), username);
                        bn_int_set(&rpacket->u.server_loginreply_w3.message, SERVER_LOGINREPLY_W3_MESSAGE_BADACCT);
                    } else if (account_get_auth_lock(account) == 1) {	/* default to false */
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] login for \"%s\" refused (this account is locked)", conn_get_socket(c), username);
                        bn_int_set(&rpacket->u.server_loginreply1.message, SERVER_LOGINREPLY_W3_MESSAGE_BADACCT);
                    } else {
                        eventlog(eventlog_level_info, __FUNCTION__, "[%d] (W3) \"%s\" passed account check", conn_get_socket(c), username);
                        conn_set_loggeduser(c, username);
                        bn_int_set(&rpacket->u.server_loginreply_w3.message, SERVER_LOGINREPLY_W3_MESSAGE_SUCCESS);
                    }
        }

        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);

    }

    return 0;
}

static int _client_pingreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_pingreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad PINGREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_pingreq), packet_get_size(packet));
        return -1;
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_pingreply));
        packet_set_type(rpacket, SERVER_PINGREPLY);
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_logonproofreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_logonproofreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad LOGONPROOFREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_logonproofreq), packet_get_size(packet));
        return -1;
    }

    {
        char const *username;
        t_account *account;

        eventlog(eventlog_level_info, __FUNCTION__, "[%d] logon proof requested", conn_get_socket(c));

        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;
        packet_set_size(rpacket, sizeof(t_server_logonproofreply));
        packet_set_type(rpacket, SERVER_LOGONPROOFREPLY);

        bn_int_set(&rpacket->u.server_logonproofreply.response, SERVER_LOGONPROOFREPLY_RESPONSE_BADPASS);

        bn_int_set(&rpacket->u.server_logonproofreply.unknown1, SERVER_LOGONPROOFREPLY_UNKNOWN1);

        bn_short_set(&rpacket->u.server_logonproofreply.port0, (short) 0x0000);

        bn_int_set(&rpacket->u.server_logonproofreply.unknown2, SERVER_LOGONPROOFREPLY_UNKNOWN2);

        bn_short_set(&rpacket->u.server_logonproofreply.port1, (short) 0x0000);

        bn_int_set(&rpacket->u.server_logonproofreply.unknown3, SERVER_LOGONPROOFREPLY_UNKNOWN3);
        bn_int_set(&rpacket->u.server_logonproofreply.unknown4, SERVER_LOGONPROOFREPLY_UNKNOWN4);

        if (!(username = conn_get_loggeduser(c))) {
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] (W3) got NULL username, 0x54ff before 0x53ff?", conn_get_socket(c));
        } else if (!(account = accountlist_find_account(username))) {
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] (W3) login in 0x54ff for \"%s\" refused (no such account)", conn_get_socket(c), username);
        } else {
            t_hash serverhash;
            t_hash clienthash;

            if (!packet_get_data_const(packet, 4, 20)) {
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] (W3) got bad LOGONPROOFREQ packet (missing hash)", conn_get_socket(c));
                return -1;
            }
            // endian fix
            clienthash[0] = bn_int_get(packet->u.client_logonproofreq.password_hash1[0]);
            clienthash[1] = bn_int_get(packet->u.client_logonproofreq.password_hash1[1]);
            clienthash[2] = bn_int_get(packet->u.client_logonproofreq.password_hash1[2]);
            clienthash[3] = bn_int_get(packet->u.client_logonproofreq.password_hash1[3]);
            clienthash[4] = bn_int_get(packet->u.client_logonproofreq.password_hash1[4]);

            hash_set_str(&serverhash, account_get_pass(account));
            if (hash_eq(clienthash, serverhash)) {
                conn_login(c, account, username);
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] (W3) \"%s\" logged in (right password)", conn_get_socket(c), username);
                if ((conn_get_versionid(c) >= 0x0000000D) && (account_get_email(account) == NULL))
                    bn_int_set(&rpacket->u.server_logonproofreply.response, SERVER_LOGONPROOFREPLY_RESPONSE_EMAIL);
                else
                    bn_int_set(&rpacket->u.server_logonproofreply.response, SERVER_LOGONPROOFREPLY_RESPONSE_OK);
                // by amadeo updates the userlist
#ifdef WIN32_GUI
                guiOnUpdateUserList();
#endif
            } else {
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] (W3) got wrong password for \"%s\"", conn_get_socket(c), username);
                conn_increment_passfail_count(c);
            }
        }
		conn_push_outqueue(c, rpacket);
		packet_del_ref(rpacket);
    }
    clan_send_status_window(c);

    return 0;
}

static int _client_changegameport(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_changegameport)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad changegameport packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_changegameport), packet_get_size(packet));
        return -1;
    }
    {
        unsigned short port = bn_short_get(packet->u.client_changegameport.port);
        if (port < 1024) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] invalid port in changegameport packet: %d", conn_get_socket(c), (int) port);
            return -1;
        }

        conn_set_game_port(c, port);
    }

    return 0;
}

static int _client_friendslistreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_friendslistreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad FRIENDSLISTREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_friendslistreq), packet_get_size(packet));
        return -1;
    }
    {
        int friend_;
        t_list *flist;
        t_friend *fr;
        t_account *account = conn_get_account(c);
        int i;
        int n = account_get_friendcount(account);
        int friendcount = 0;
        t_server_friendslistreply_status status;
        t_connection *dest_c;
        t_game *game;
        t_channel *channel;
        char stat;

        if (n == 0)
            return 0;
        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;

        packet_set_size(rpacket, sizeof(t_server_friendslistreply));
        packet_set_type(rpacket, SERVER_FRIENDSLISTREPLY);


        if ((flist = account_get_friends(account)) == NULL)
            return -1;

        for (i = 0; i < n; i++) {
            friend_ = account_get_friend(account, i);
            if ((fr = friendlist_find_uid(flist, friend_)) == NULL)
                continue;
            packet_append_string(rpacket, account_get_name(friend_get_account(fr)));
            game = NULL;
            channel = NULL;

            if (!(dest_c = connlist_find_connection_by_uid(friend_))) {
                bn_byte_set(&status.location, FRIENDSTATUS_OFFLINE);
                bn_byte_set(&status.status, 0);
                bn_int_set(&status.clienttag, 0);
            } else {
                bn_int_set(&status.clienttag, conn_get_clienttag(dest_c));
                stat = 0;
                if ((friend_get_mutual(fr)))
                    stat |= FRIEND_TYPE_MUTUAL;
                if ((conn_get_dndstr(dest_c,0)))
                    stat |= FRIEND_TYPE_DND;
                if ((conn_get_awaystr(dest_c)))
                    stat |= FRIEND_TYPE_AWAY;
                bn_byte_set(&status.status, stat);
                if ((game = conn_get_game(dest_c))) {
                    if (game_get_flag(game) != game_flag_private)
                        bn_byte_set(&status.location, FRIENDSTATUS_PUBLIC_GAME);
                    else
                        bn_byte_set(&status.location, FRIENDSTATUS_PRIVATE_GAME);
                } else if ((channel = conn_get_channel(dest_c))) {
                    bn_byte_set(&status.location, FRIENDSTATUS_CHAT);
                } else {
                    bn_byte_set(&status.location, FRIENDSTATUS_ONLINE);
                }
            }

            packet_append_data(rpacket, &status, sizeof(status));

            if (game)
                packet_append_string(rpacket, game_get_name(game));
            else if (channel)
                packet_append_string(rpacket, channel_get_name(channel));
            else
                packet_append_string(rpacket, "");

            friendcount++;
        }

        bn_byte_set(&rpacket->u.server_friendslistreply.friendcount, friendcount);

        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_friendinforeq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_friendinforeq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad FRIENDINFOREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_friendinforeq), packet_get_size(packet));
        return -1;
    }

    {
        t_connection const *dest_c;
        t_game const *game;
        t_channel const *channel;
        t_account *account = conn_get_account(c);
        int friend_;
        t_friend *fr;
        t_list *flist;
        int n = account_get_friendcount(account);
        char type;

        if (bn_byte_get(packet->u.client_friendinforeq.friendnum) > n) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] bad friend_ number in FRIENDINFOREQ packet", conn_get_socket(c));
            return -1;
        }

        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;

        packet_set_size(rpacket, sizeof(t_server_friendinforeply));
        packet_set_type(rpacket, SERVER_FRIENDINFOREPLY);

        friend_ = account_get_friend(account, bn_byte_get(packet->u.client_friendinforeq.friendnum));
        if (friend_ < 0) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] friend_ number %d not found", conn_get_socket(c), (int) bn_byte_get(packet->u.client_friendinforeq.friendnum));
            return -1;
        }

        bn_byte_set(&rpacket->u.server_friendinforeply.friendnum, bn_byte_get(packet->u.client_friendinforeq.friendnum));

        flist = account_get_friends(account);
        fr = friendlist_find_uid(flist, friend_);

        if (fr == NULL || (dest_c = connlist_find_connection_by_account(friend_get_account(fr))) == NULL) {
            bn_byte_set(&rpacket->u.server_friendinforeply.type, FRIEND_TYPE_NON_MUTUAL);
            bn_byte_set(&rpacket->u.server_friendinforeply.status, FRIENDSTATUS_OFFLINE);
            bn_int_set(&rpacket->u.server_friendinforeply.clienttag, 0);
            packet_append_string(rpacket, "");
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
            return 0;
        }

        type = FRIEND_TYPE_NON_MUTUAL;
        if (friend_get_mutual(fr))
            type |= FRIEND_TYPE_MUTUAL;
        if ((conn_get_dndstr(dest_c,0)))
            type |= FRIEND_TYPE_DND;
        if ((conn_get_awaystr(dest_c)))
            type |= FRIEND_TYPE_AWAY;
        bn_byte_set(&rpacket->u.server_friendinforeply.type, type);
        if ((game = conn_get_game(dest_c))) {
            if (game_get_flag(game) != game_flag_private)
                bn_byte_set(&rpacket->u.server_friendinforeply.status, FRIENDSTATUS_PUBLIC_GAME);
            else
                bn_byte_set(&rpacket->u.server_friendinforeply.status, FRIENDSTATUS_PRIVATE_GAME);
            packet_append_string(rpacket, game_get_name(game));
        } else if ((channel = conn_get_channel(dest_c))) {
            bn_byte_set(&rpacket->u.server_friendinforeply.status, FRIENDSTATUS_CHAT);
            packet_append_string(rpacket, channel_get_name(channel));
        } else {
            bn_byte_set(&rpacket->u.server_friendinforeply.status, FRIENDSTATUS_ONLINE);
            packet_append_string(rpacket, "");
        }

        bn_int_set(&rpacket->u.server_friendinforeply.clienttag, conn_get_clienttag(dest_c));

        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);

    }

    return 0;
}

static int _client_atfriendscreen(t_connection * c, t_packet const *const packet)
{
    char const *myusername;
    char const *fname;
    t_connection *dest_c;
    unsigned char f_cnt = 0;
    t_account *account;
    t_packet *rpacket;
    char const *vt;
    char const *nvt;
    t_friend *fr;
    t_list *flist;
    t_elem *curr;
    t_channel *mychannel, *chan;
    int publicchan = 1;

    eventlog(eventlog_level_info, __FUNCTION__, "[%d] got CLIENT_ARRANGEDTEAM_FRIENDSCREEN packet", conn_get_socket(c));

    myusername = conn_get_username(c);
    eventlog(eventlog_level_trace, "handle_bnet", "[%d] AT - Got Username %s", conn_get_socket(c), myusername);

    if (!(rpacket = packet_create(packet_class_bnet))) {
        eventlog(eventlog_level_error, "handle_bnet", "[%d] AT - can't create friendscreen server packet", conn_get_socket(c));
        return -1;
    }

    packet_set_size(rpacket, sizeof(t_server_arrangedteam_friendscreen));
    packet_set_type(rpacket, SERVER_ARRANGEDTEAM_FRIENDSCREEN);


    mychannel = conn_get_channel(c);
    if ((mychannel))
        publicchan = channel_get_flags(mychannel) & channel_flags_public;

    vt = versioncheck_get_versiontag(conn_get_versioncheck(c));
    flist = account_get_friends(conn_get_account(c));
    LIST_TRAVERSE(flist, curr) {
        if (!(fr = (t_friend*)elem_get_data(curr))) {
            eventlog(eventlog_level_error, __FUNCTION__, "found NULL entry in list");
            continue;
        }

        account = friend_get_account(fr);
        if (!(dest_c = connlist_find_connection_by_account(account)))
            continue;		// if user is offline, then continue to next friend
        nvt = versioncheck_get_versiontag(conn_get_versioncheck(dest_c));
        if (vt && nvt && strcmp(vt, nvt))
            continue;		/* friend is using another game/version */

        if (friend_get_mutual(fr)) {
            if (conn_get_dndstr(dest_c,0))
                continue;	// user is dnd
            if (conn_get_awaystr(dest_c))
                continue;	// user is away
            if (conn_get_game(dest_c))
                continue;	// user is some game
            if (!(chan = conn_get_channel(dest_c)))
                continue;
            if (!publicchan && (chan == mychannel))
                continue;	// don't list YET if in same private channel

            fname = account_get_name(account);
            eventlog(eventlog_level_trace, "handle_bnet", "AT - Friend: %s is available for a AT Game.", fname);
            f_cnt++;
            packet_append_string(rpacket, fname);
        }
    }

    if (!publicchan) {		// now list matching users in same private chan
        for (dest_c = channel_get_first(mychannel); dest_c; dest_c = channel_get_next()) {
            if (dest_c == c)
                continue;	// don'tlist yourself
            nvt = versioncheck_get_versiontag(conn_get_versioncheck(dest_c));
            if (vt && nvt && strcmp(vt, nvt))
                continue;	/* user is using another game/version */
            if (conn_get_dndstr(dest_c,0))
                continue;	// user is dnd
            if (conn_get_awaystr(dest_c))
                continue;	// user is away
            if (!(conn_get_account(dest_c)))
                continue;
            fname = account_get_name(conn_get_account(dest_c));
            eventlog(eventlog_level_trace, "handle_bnet", "AT - user in private channel: %s is available for a AT Game.", fname);
            f_cnt++;
            packet_append_string(rpacket, fname);
        }
    }

    if (!f_cnt)
        eventlog(eventlog_level_info, "handle_bnet", "AT - no friends available for AT game.");
    bn_byte_set(&rpacket->u.server_arrangedteam_friendscreen.f_count, f_cnt);
    conn_push_outqueue(c, rpacket);

    packet_del_ref(rpacket);

    return 0;
}

static int _client_atinvitefriend(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    t_clienttag ctag;

    if (packet_get_size(packet) < sizeof(t_client_arrangedteam_invite_friend)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad ARRANGEDTEAM_INVITE_FRIEND packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_arrangedteam_invite_friend), packet_get_size(packet));
        return -1;
    }

    ctag = conn_get_clienttag(c);

    {
        int count_to_invite, count, id;
        char const *invited_usernames[8];
        t_account *members[MAX_TEAMSIZE];
        int i, n, offset, teammemcount;
        t_connection *dest_c;
        t_team *team;
        unsigned int teamid;

        count_to_invite = bn_byte_get(packet->u.client_arrangedteam_invite_friend.numfriends);
        count = bn_int_get(packet->u.client_arrangedteam_invite_friend.count);
        id = bn_int_get(packet->u.client_arrangedteam_invite_friend.id);
        teammemcount = count_to_invite + 1;

        if ((count_to_invite < 1) || (count_to_invite > 3)) {
            eventlog(eventlog_level_error, __FUNCTION__, "got invalid number of users to invite to game");
            return -1;
        }

        eventlog(eventlog_level_info, __FUNCTION__, "[%d] got ARRANGEDTEAM INVITE packet for %d invitees", conn_get_socket(c), count_to_invite);

        offset = sizeof(t_client_arrangedteam_invite_friend);

        for (i = 0; i < count_to_invite; i++) {
            if (!(invited_usernames[i] = packet_get_str_const(packet, offset, USER_NAME_MAX))) {
                eventlog(eventlog_level_error, "handle_bnet", "Could not get username from invite packet");
                return -1;
            } else {
                offset += strlen(invited_usernames[i]) + 1;
                eventlog(eventlog_level_debug, "handle_bnet", "Added user %s to invite array.", invited_usernames[i]);
            }
        }

        members[0] = conn_get_account(c);
        for (i = 1; i < MAX_TEAMSIZE; i++) {
            if ((i < teammemcount)) {
                if (!(members[i] = accountlist_find_account(invited_usernames[i - 1]))) {
                    eventlog(eventlog_level_error, __FUNCTION__, "got invitation for non-existant user \"%s\"", invited_usernames[i - 1]);
                    return -1;
                }
            } else
                members[i] = NULL;
        }


        if (!(team = account_find_team_by_accounts(members[0], members, ctag))) {
            team = create_team(members, ctag);	//no need to free on return -1 because it's allready in teamlist

            eventlog(eventlog_level_trace, __FUNCTION__, "this team has never played before, creating new team");
        } else {
            eventlog(eventlog_level_trace, __FUNCTION__, "this team has allready played before");
        }

        teamid = team_get_teamid(team);
        account_set_currentatteam(conn_get_account(c), team_get_teamid(team));


        //Create the packet to send to each of the users you wanted to invite
        conn_set_channel(c, NULL);

        for (i = 0; i < teammemcount; i++) {

            if (!(dest_c = account_get_conn(team_get_member(team, i))))
                continue;

            if ((dest_c == c))
                continue;

            if (!(rpacket = packet_create(packet_class_bnet)))
                return -1;

            packet_set_size(rpacket, sizeof(t_server_arrangedteam_send_invite));
            packet_set_type(rpacket, SERVER_ARRANGEDTEAM_SEND_INVITE);

            bn_int_set(&rpacket->u.server_arrangedteam_send_invite.count, count);
            bn_int_set(&rpacket->u.server_arrangedteam_send_invite.id, id);
            {			/* trans support */
                unsigned short port = conn_get_game_port(c);
                unsigned int addr = conn_get_addr(c);

                trans_net(conn_get_addr(dest_c), &addr, &port);

                bn_int_nset(&rpacket->u.server_arrangedteam_send_invite.inviterip, addr);
                bn_short_set(&rpacket->u.server_arrangedteam_send_invite.port, port);
            }
            bn_byte_set(&rpacket->u.server_arrangedteam_send_invite.numfriends, count_to_invite);

            for (n = 0; n < teammemcount; n++) {
                if (n != i)
                    packet_append_string(rpacket, account_get_name(team_get_member(team, n)));
            }

            //now send packet
            conn_push_outqueue(dest_c, rpacket);
            packet_del_ref(rpacket);

            account_set_currentatteam(conn_get_account(dest_c), teamid);
        }

        //now send a ACK to the inviter
        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;
        packet_set_size(rpacket, sizeof(t_server_arrangedteam_invite_friend_ack));
        packet_set_type(rpacket, SERVER_ARRANGEDTEAM_INVITE_FRIEND_ACK);

        bn_int_set(&rpacket->u.server_arrangedteam_invite_friend_ack.count, count);
        bn_int_set(&rpacket->u.server_arrangedteam_invite_friend_ack.id, id);
        bn_int_set(&rpacket->u.server_arrangedteam_invite_friend_ack.timestamp, now);
        bn_byte_set(&rpacket->u.server_arrangedteam_invite_friend_ack.teamsize, count_to_invite + 1);

        /*
         * five int's to fill
         * fill with uid's of all teammembers, including the inviter
         * and the rest with FFFFFFFF
         * to be used when sever recieves anongame search
         * [Omega]
         */
        for (i = 0; i < 5; i++) {

            if (i < teammemcount) {
                bn_int_set(&rpacket->u.server_arrangedteam_invite_friend_ack.info[i], team_get_memberuid(team, i));
            } else {		/* fill rest with FFFFFFFF */
                bn_int_set(&rpacket->u.server_arrangedteam_invite_friend_ack.info[i], 0xFFFFFFFF);
            }
        }

        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);

    }

    return 0;
}

static int _client_atacceptdeclineinvite(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    t_clienttag ctag;

    if (packet_get_size(packet) < sizeof(t_client_arrangedteam_accept_decline_invite)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad ARRANGEDTEAM_ACCEPT_DECLINE_INVITE packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_arrangedteam_accept_decline_invite), packet_get_size(packet));
        return -1;
    }

    ctag = conn_get_clienttag(c);

    {
        char const *inviter;
        t_connection *dest_c;

        //if user declined the invitation then
        if (bn_int_get(packet->u.client_arrangedteam_accept_decline_invite.option) == CLIENT_ARRANGEDTEAM_DECLINE) {
            inviter = packet_get_str_const(packet, sizeof(t_client_arrangedteam_accept_decline_invite), USER_NAME_MAX);
            dest_c = connlist_find_connection_by_accountname(inviter);

            eventlog(eventlog_level_info, "handle_bnet", "%s declined a arranged team game with %s", conn_get_username(c), inviter);

            if (!(rpacket = packet_create(packet_class_bnet)))
                return -1;
            packet_set_size(rpacket, sizeof(t_server_arrangedteam_member_decline));
            packet_set_type(rpacket, SERVER_ARRANGEDTEAM_MEMBER_DECLINE);

            bn_int_set(&rpacket->u.server_arrangedteam_member_decline.count, bn_int_get(packet->u.client_arrangedteam_accept_decline_invite.count));
            bn_int_set(&rpacket->u.server_arrangedteam_member_decline.action, SERVER_ARRANGEDTEAM_DECLINE);
            packet_append_string(rpacket, conn_get_username(c));

            conn_push_outqueue(dest_c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_atacceptinvite(t_connection * c, t_packet const *const packet)
{
    // t_packet * rpacket;

    if (packet_get_size(packet) < sizeof(t_client_arrangedteam_accept_invite)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad ARRANGEDTEAM_ACCEPT_INVITE packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_arrangedteam_accept_invite), packet_get_size(packet));
        return -1;
    }
    /* conn_set_channel(c, "Arranged Teams"); */
    return 0;
}

typedef struct {
    t_connection *c;
    unsigned lnews;
    unsigned fnews;
} t_motd_data;

static int _news_cb(time_t date, t_lstr * lstr, void *data)
{
    t_packet *rpacket;
    t_motd_data *motdd = (t_motd_data *) data;

    if (date < motdd->lnews)
        return -1;		/* exit traversing */

    rpacket = packet_create(packet_class_bnet);
    if (!rpacket)
        return -1;

    packet_set_size(rpacket, sizeof(t_server_motd_w3));
    packet_set_type(rpacket, SERVER_MOTD_W3);

    bn_byte_set(&rpacket->u.server_motd_w3.msgtype, SERVER_MOTD_W3_MSGTYPE);
    bn_int_set(&rpacket->u.server_motd_w3.curr_time, now);

    bn_int_set(&rpacket->u.server_motd_w3.first_news_time, motdd->fnews);
    bn_int_set(&rpacket->u.server_motd_w3.timestamp, date);
    bn_int_set(&rpacket->u.server_motd_w3.timestamp2, date);

    /* Append news to packet, we used the already cached len in the lstr */
    packet_append_lstr(rpacket, lstr);

    /* Send news packet */
    conn_push_outqueue(motdd->c, rpacket);
    packet_del_ref(rpacket);

    return 0;
}

static int _client_motdw3(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    char serverinfo[512];
    t_clienttag ctag;
    t_motd_data motdd;

    if (packet_get_size(packet) < sizeof(t_client_motd_w3)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CLIENT_MOTD_W3 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_motd_w3), packet_get_size(packet));
        return -1;
    }

    ctag = conn_get_clienttag(c);
    /* if in a game, remove user from his game */
    if (conn_get_game(c) != NULL)
        conn_set_game(c, NULL, NULL, NULL, game_type_none, 0);

    /* News */
    motdd.lnews = bn_int_get(packet->u.client_motd_w3.last_news_time);
    motdd.fnews = news_get_firstnews();
    motdd.c = c;

    eventlog(eventlog_level_trace, __FUNCTION__, "lastnews() %u news_time %u", news_get_lastnews(), motdd.lnews);
    news_traverse(_news_cb, &motdd);

    /* Welcome Message */
    rpacket = packet_create(packet_class_bnet);
    if (!rpacket)
        return -1;

    packet_set_size(rpacket, sizeof(t_server_motd_w3));
    packet_set_type(rpacket, SERVER_MOTD_W3);

    //bn_int_set(&rpacket->u.server_motd_w3.ticks,get_ticks());
    bn_byte_set(&rpacket->u.server_motd_w3.msgtype, SERVER_MOTD_W3_MSGTYPE);
    bn_int_set(&rpacket->u.server_motd_w3.curr_time, now);
    bn_int_set(&rpacket->u.server_motd_w3.first_news_time, motdd.fnews);
    bn_int_set(&rpacket->u.server_motd_w3.timestamp, motdd.fnews + 1);
    bn_int_set(&rpacket->u.server_motd_w3.timestamp2, SERVER_MOTD_W3_WELCOME);

    sprintf(serverinfo, "Welcome to the " PVPGN_SOFTWARE " Version " PVPGN_VERSION "\r\n\r\nThere are currently %u user(s) in %u games of %s, and %u user(s) playing %u games and chatting in %u channels in %s.\r\n%s", conn_get_user_count_by_clienttag(conn_get_clienttag(c)), game_get_count_by_clienttag(ctag), clienttag_get_title(conn_get_clienttag(c)), connlist_login_get_length(), gamelist_get_length(), channellist_get_length(), prefs_get_servername(),prefs_get_server_info());

    packet_append_string(rpacket, serverinfo);

    conn_push_outqueue(c, rpacket);
    packet_del_ref(rpacket);

    return 0;
}

static int _client_realmlistreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_realmlistreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad REALMLISTREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_realmlistreq), packet_get_size(packet));
        return -1;
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        t_elem const *curr;
        t_realm const *realm;
        t_server_realmlistreply_data realmdata;
        unsigned int count;

        packet_set_size(rpacket, sizeof(t_server_realmlistreply));
        packet_set_type(rpacket, SERVER_REALMLISTREPLY);
        bn_int_set(&rpacket->u.server_realmlistreply.unknown1, SERVER_REALMLISTREPLY_UNKNOWN1);
        count = 0;
        LIST_TRAVERSE_CONST(realmlist(), curr) {
            realm = (const t_realm*)elem_get_data(curr);
            if (!realm_get_active(realm))
                continue;
            bn_int_set(&realmdata.unknown3, SERVER_REALMLISTREPLY_DATA_UNKNOWN3);
            bn_int_set(&realmdata.unknown4, SERVER_REALMLISTREPLY_DATA_UNKNOWN4);
            bn_int_set(&realmdata.unknown5, SERVER_REALMLISTREPLY_DATA_UNKNOWN5);
            bn_int_set(&realmdata.unknown6, SERVER_REALMLISTREPLY_DATA_UNKNOWN6);
            bn_int_set(&realmdata.unknown7, SERVER_REALMLISTREPLY_DATA_UNKNOWN7);
            bn_int_set(&realmdata.unknown8, SERVER_REALMLISTREPLY_DATA_UNKNOWN8);
            bn_int_set(&realmdata.unknown9, SERVER_REALMLISTREPLY_DATA_UNKNOWN9);
            packet_append_data(rpacket, &realmdata, sizeof(realmdata));
            packet_append_string(rpacket, realm_get_name(realm));
            packet_append_string(rpacket, realm_get_description(realm));
            count++;
        }
        bn_int_set(&rpacket->u.server_realmlistreply.count, count);
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_realmlistreq110(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_realmlistreq_110)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad REALMLISTREQ_110 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_realmlistreq), packet_get_size(packet));
        return -1;
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        t_elem const *curr;
        t_realm const *realm;
        t_server_realmlistreply_110_data realmdata;
        unsigned int count;

        packet_set_size(rpacket, sizeof(t_server_realmlistreply_110));
        packet_set_type(rpacket, SERVER_REALMLISTREPLY_110);
        bn_int_set(&rpacket->u.server_realmlistreply_110.unknown1, SERVER_REALMLISTREPLY_110_UNKNOWN1);
        count = 0;
        LIST_TRAVERSE_CONST(realmlist(), curr) {
            realm = (const t_realm*)elem_get_data(curr);
            if (!realm_get_active(realm))
                continue;
            bn_int_set(&realmdata.unknown1, SERVER_REALMLISTREPLY_110_DATA_UNKNOWN1);
            packet_append_data(rpacket, &realmdata, sizeof(realmdata));
            packet_append_string(rpacket, realm_get_name(realm));
            packet_append_string(rpacket, realm_get_description(realm));
            count++;
        }
        bn_int_set(&rpacket->u.server_realmlistreply_110.count, count);
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_claninforeq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    int count;
    char const *username;
    t_account *account;
    t_clanmember *clanmember;
    t_clan *clan;
    int clantag1;
    int clantag2;

    if (packet_get_size(packet) < sizeof(t_client_claninforeq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CLANINFOREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_claninforeq), packet_get_size(packet));
        return -1;
    }

    count = bn_int_get(packet->u.client_claninforeq.count);
    clantag1 = bn_int_get(packet->u.client_claninforeq.clantag);
    clan = NULL;

    if (!(username = packet_get_str_const(packet, sizeof(t_client_claninforeq), USER_NAME_MAX))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CLANINFOREQ (missing or too long username)", conn_get_socket(c));
        return -1;
    }

    if (!(account = accountlist_find_account(username))) {
        eventlog(eventlog_level_error, __FUNCTION__, "requested claninfo for non-existant account");
        return -1;
    }

    if ((clanmember = account_get_clanmember(account)) && (clan = clanmember_get_clan(clanmember)))
        clantag2 = clan_get_clantag(clan);
    else
        clantag2 = 0;

    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_claninforeply));
        packet_set_type(rpacket, SERVER_CLANINFOREPLY);
        bn_int_set(&rpacket->u.server_profilereply.count, count);
        if (clantag1 == clantag2) {
            int temp;
            t_bnettime bn_time;
            bn_long ltime;

            bn_byte_set(&rpacket->u.server_claninforeply.fail, 0);

            packet_append_string(rpacket, clan_get_name(clan));
            temp = clanmember_get_status(clanmember);
            packet_append_data(rpacket, &temp, 1);
            temp = clanmember_get_join_time(clanmember);
            bn_time = time_to_bnettime(temp, 0);
            bn_time = bnettime_add_tzbias(bn_time, -conn_get_tzbias(c));
            bnettime_to_bn_long(bn_time, &ltime);
            packet_append_data(rpacket, &ltime, 8);
        } else
            bn_byte_set(&rpacket->u.server_claninforeply.fail, 1);


        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_profilereq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    int count;
    char const *username;
    t_account *account;
    t_clanmember *clanmember;
    t_clan *clan;
    bn_int clanTAG;

    if (packet_get_size(packet) < sizeof(t_client_profilereq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad PROFILEREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_profilereq), packet_get_size(packet));
        return -1;
    }

    count = bn_int_get(packet->u.client_profilereq.count);

    if (!(username = packet_get_str_const(packet, sizeof(t_client_profilereq), USER_NAME_MAX))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad PROFILEREQ (missing or too long username)", conn_get_socket(c));
        return -1;
    }

    if (!(account = accountlist_find_account(username))) {
        eventlog(eventlog_level_error, __FUNCTION__, "requested profile for non-existant account");
        return -1;
    }
    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_profilereply));
        packet_set_type(rpacket, SERVER_PROFILEREPLY);
        bn_int_set(&rpacket->u.server_profilereply.count, count);
        bn_byte_set(&rpacket->u.server_profilereply.fail, 0);
        packet_append_string(rpacket, account_get_desc(account));
        packet_append_string(rpacket, account_get_loc(account));
        if ((clanmember = account_get_clanmember(account)) && (clan = clanmember_get_clan(clanmember)))
            bn_int_set(&clanTAG, clan_get_clantag(clan));
        else
            bn_int_set(&clanTAG, 0);
        packet_append_data(rpacket, clanTAG, 4);

        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}


static int _client_realmjoinreq109(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_realmjoinreq_109)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad REALMJOINREQ_109 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_realmjoinreq_109), packet_get_size(packet));
        return -1;
    }

    {
        char const *realmname;
        t_realm *realm;

        if (!(realmname = packet_get_str_const(packet, sizeof(t_client_realmjoinreq_109), REALM_NAME_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad REALMJOINREQ_109 (missing or too long realmname)", conn_get_socket(c));
            return -1;
        }

        if ((realm = realmlist_find_realm(realmname))) {
            unsigned int salt;
            struct {
                bn_int salt;
                bn_int sessionkey;
                bn_int sessionnum;
                bn_int secret;
                bn_int passhash[5];
            } temp;
            char const *pass_str;
            t_hash secret_hash;
            t_hash passhash;
            t_realm *prev_realm;

            /* FIXME: should we only set this after they log in to the realm server? */
            prev_realm = conn_get_realm(c);
            if (prev_realm) {
                if (prev_realm != realm) {
                    realm_add_player_number(realm, 1);
                    realm_add_player_number(prev_realm, -1);
                    conn_set_realm(c, realm);
                }
            } else {
                realm_add_player_number(realm, 1);
                conn_set_realm(c, realm);
            }

            if ((pass_str = account_get_pass(conn_get_account(c)))) {
                if (hash_set_str(&passhash, pass_str) == 0) {
                    hash_to_bnhash((t_hash const *) &passhash, temp.passhash);
                    salt = bn_int_get(packet->u.client_realmjoinreq_109.seqno);
                    bn_int_set(&temp.salt, salt);
                    bn_int_set(&temp.sessionkey, conn_get_sessionkey(c));
                    bn_int_set(&temp.sessionnum, conn_get_sessionnum(c));
                    bn_int_set(&temp.secret, conn_get_secret(c));
                    bnet_hash(&secret_hash, sizeof(temp), &temp);

                    if ((rpacket = packet_create(packet_class_bnet))) {
                        packet_set_size(rpacket, sizeof(t_server_realmjoinreply_109));
                        packet_set_type(rpacket, SERVER_REALMJOINREPLY_109);
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.seqno, salt);
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.u1, 0x0);
                        bn_short_set(&rpacket->u.server_realmjoinreply_109.u3, 0x0);	/* reg auth */
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.bncs_addr1, 0x0);
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.sessionnum, conn_get_sessionnum(c));
                        {	/* trans support */
                            unsigned int addr = realm_get_ip(realm);
                            unsigned short port = realm_get_port(realm);

                            trans_net(conn_get_addr(c), &addr, &port);

                            bn_int_nset(&rpacket->u.server_realmjoinreply_109.addr, addr);
                            bn_short_nset(&rpacket->u.server_realmjoinreply_109.port, port);
                        }
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.sessionkey, conn_get_sessionkey(c));
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.u5, 0);
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.u6, 0);
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.bncs_addr2, 0);
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.u7, 0);
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.versionid, conn_get_versionid(c));
                        bn_int_set(&rpacket->u.server_realmjoinreply_109.clienttag, conn_get_clienttag(c));
                        hash_to_bnhash((t_hash const *) &secret_hash, rpacket->u.server_realmjoinreply_109.secret_hash);	/* avoid warning */
                        packet_append_string(rpacket, conn_get_username(c));
                        conn_push_outqueue(c, rpacket);
                        packet_del_ref(rpacket);
                    }
                    return 0;
                } else
                    eventlog(eventlog_level_info, __FUNCTION__, "[%d] realm join for \"%s\" failed (unable to hash password)", conn_get_socket(c), conn_get_loggeduser(c));
            } else {
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] realm join for \"%s\" failed (no password)", conn_get_socket(c), conn_get_loggeduser(c));
            }
        } else
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] could not find active realm \"%s\"", conn_get_socket(c), realmname);

        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_realmjoinreply_109));
            packet_set_type(rpacket, SERVER_REALMJOINREPLY_109);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.seqno, bn_int_get(packet->u.client_realmjoinreq_109.seqno));
            bn_int_set(&rpacket->u.server_realmjoinreply_109.u1, 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.sessionnum, 0);
            bn_short_set(&rpacket->u.server_realmjoinreply_109.u3, 0);
            bn_int_nset(&rpacket->u.server_realmjoinreply_109.addr, 0);
            bn_short_nset(&rpacket->u.server_realmjoinreply_109.port, 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.sessionkey, 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.u5, 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.u6, 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.u7, 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.bncs_addr1, 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.bncs_addr2, 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.versionid, 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.clienttag, 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.secret_hash[0], 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.secret_hash[1], 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.secret_hash[2], 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.secret_hash[3], 0);
            bn_int_set(&rpacket->u.server_realmjoinreply_109.secret_hash[4], 0);
            packet_append_string(rpacket, "");
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_charlistreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_unknown_37)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad UNKNOWN_37 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_unknown_37), packet_get_size(packet));
        return -1;
    }
    /*
       0x0070:                           83 80 ff ff ff ff ff 2f    t,taran,......./
       0x0080: ff ff ff ff ff ff ff ff   ff ff 03 ff ff ff ff ff    ................
       0x0090: ff ff ff ff ff ff ff ff   ff ff ff 07 80 80 80 80    ................
       0x00a0: ff ff ff 00
     */
    if ((rpacket = packet_create(packet_class_bnet))) {
        char const *charlist;
        char *temp;

        packet_set_size(rpacket, sizeof(t_server_unknown_37));
        packet_set_type(rpacket, SERVER_UNKNOWN_37);
        bn_int_set(&rpacket->u.server_unknown_37.unknown1, SERVER_UNKNOWN_37_UNKNOWN1);
        bn_int_set(&rpacket->u.server_unknown_37.unknown2, SERVER_UNKNOWN_37_UNKNOWN2);

        if (!(charlist = account_get_closed_characterlist(conn_get_account(c), conn_get_clienttag(c), realm_get_name(conn_get_realm(c))))) {
            bn_int_set(&rpacket->u.server_unknown_37.count, 0);
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
            return 0;
        }
        temp = xstrdup(charlist);

        {
            char const *tok1;
            char const *tok2;
            t_character const *ch;
            unsigned int count;

            count = 0;
            tok1 = (char const *) strtok(temp, ",");	/* strtok modifies the string it is passed */
            tok2 = strtok(NULL, ",");
            while (tok1) {
                if (!tok2) {
                    eventlog(eventlog_level_error, __FUNCTION__, "[%d] account \"%s\" has bad character list \"%s\"", conn_get_socket(c), conn_get_username(c), temp);
                    break;
                }

                if ((ch = characterlist_find_character(tok1, tok2))) {
                    packet_append_ntstring(rpacket, character_get_realmname(ch));
                    packet_append_ntstring(rpacket, ",");
                    packet_append_string(rpacket, character_get_name(ch));
                    packet_append_string(rpacket, character_get_playerinfo(ch));
                    packet_append_string(rpacket, character_get_guildname(ch));
                    count++;
                } else
                    eventlog(eventlog_level_error, __FUNCTION__, "[%d] character \"%s\" is missing", conn_get_socket(c), tok2);
                tok1 = strtok(NULL, ",");
                tok2 = strtok(NULL, ",");
            }
            xfree(temp);

            bn_int_set(&rpacket->u.server_unknown_37.count, count);
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_unknown39(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_unknown_39)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad UNKNOWN_39 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_unknown_39), packet_get_size(packet));
        return -1;
    }
    return 0;
}

static int _client_adreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_adreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad ADREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_adreq), packet_get_size(packet));
        return -1;
    }

    {
        t_adbanner *ad;

        if (!(ad = adbanner_pick(c, bn_int_get(packet->u.client_adreq.prev_adid))))
            return 0;

        /*                  eventlog(eventlog_level_debug,__FUNCTION__,"[%d] picking ad file=\"%s\" id=0x%06x tag=%u",conn_get_socket(c),adbanner_get_filename(ad),adbanner_get_id(ad),adbanner_get_extensiontag(ad)); */
        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_adreply));
            packet_set_type(rpacket, SERVER_ADREPLY);
            bn_int_set(&rpacket->u.server_adreply.adid, adbanner_get_id(ad));
            bn_int_set(&rpacket->u.server_adreply.extensiontag, adbanner_get_extensiontag(ad));
            file_to_mod_time(adbanner_get_filename(ad), &rpacket->u.server_adreply.timestamp);
            packet_append_string(rpacket, adbanner_get_filename(ad));
            packet_append_string(rpacket, adbanner_get_link(ad));
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_adack(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_adack)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad ADACK packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_adack), packet_get_size(packet));
        return -1;
    }

    /*
       {
       char const * tname;

       eventlog(eventlog_level_info,__FUNCTION__,"[%d] ad acknowledgement for adid 0x%04x from \"%s\"",conn_get_socket(c),bn_int_get(packet->u.client_adack.adid),(tname = conn_get_chatname(c)));
       conn_unget_chatname(c,tname);
       }
     */
    return 0;
}

static int _client_adclick(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_adclick)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad ADCLICK packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_adclick), packet_get_size(packet));
        return -1;
    }

    eventlog(eventlog_level_info, __FUNCTION__, "[%d] ad click for adid 0x%04x from \"%s\"", conn_get_socket(c), bn_int_get(packet->u.client_adclick.adid), conn_get_username(c));

    return 0;
}

static int _client_adclick2(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_adclick2)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad ADCLICK2 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_adclick2), packet_get_size(packet));
        return -1;
    }

    eventlog(eventlog_level_info, __FUNCTION__, "[%d] ad click2 for adid 0x%04hx from \"%s\"", conn_get_socket(c), bn_int_get(packet->u.client_adclick2.adid), conn_get_username(c));

    {
        t_adbanner *ad;

        if (!(ad = adbanner_get(c, bn_int_get(packet->u.client_adclick2.adid))))
            return -1;

        if ((rpacket = packet_create(packet_class_bnet))) {
            packet_set_size(rpacket, sizeof(t_server_adclickreply2));
            packet_set_type(rpacket, SERVER_ADCLICKREPLY2);
            bn_int_set(&rpacket->u.server_adclickreply2.adid, adbanner_get_id(ad));
            packet_append_string(rpacket, adbanner_get_link(ad));
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_statsupdate(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_statsupdate)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STATSUPDATE packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_statsupdate), packet_get_size(packet));
        return -1;
    }

    {
        char const *name;
        char const *key;
        char const *val;
        unsigned int name_count;
        unsigned int key_count;
        unsigned int i, j;
        unsigned int name_off;
        unsigned int keys_off;
        unsigned int key_off;
        unsigned int vals_off;
        unsigned int val_off;
        t_account *account;

        name_count = bn_int_get(packet->u.client_statsupdate.name_count);
        key_count = bn_int_get(packet->u.client_statsupdate.key_count);

        if (name_count != 1)
            eventlog(eventlog_level_warn, __FUNCTION__, "[%d] got suspicious STATSUPDATE packet (name_count=%u)", conn_get_socket(c), name_count);

        for (i = 0, name_off = sizeof(t_client_statsupdate); i < name_count && (name = packet_get_str_const(packet, name_off, UNCHECKED_NAME_STR)); i++, name_off += strlen(name) + 1);
        if (i < name_count) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STATSUPDATE packet (only %u names of %u)", conn_get_socket(c), i, name_count);
            return -1;
        }
        keys_off = name_off;

        for (i = 0, key_off = keys_off; i < key_count && (key = packet_get_str_const(packet, key_off, MAX_ATTRKEY_STR)); i++, key_off += strlen(key) + 1);
        if (i < key_count) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STATSUPDATE packet (only %u keys of %u)", conn_get_socket(c), i, key_count);
            return -1;
        }
        vals_off = key_off;

        if ((account = conn_get_account(c))) {
            if (account_get_auth_changeprofile(account) == 0) {	/* default to true */
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] stats update for \"%s\" refused (no change profile access)", conn_get_socket(c), conn_get_username(c));
                return -1;
            }
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] updating player profile for \"%s\"", conn_get_socket(c), conn_get_username(c));

            for (i = 0, name_off = sizeof(t_client_statsupdate); i < name_count && (name = packet_get_str_const(packet, name_off, UNCHECKED_NAME_STR)); i++, name_off += strlen(name) + 1)
                for (j = 0, key_off = keys_off, val_off = vals_off; j < key_count && (key = packet_get_str_const(packet, key_off, MAX_ATTRKEY_STR)) && (val = packet_get_str_const(packet, val_off, MAX_ATTRVAL_STR)); j++, key_off += strlen(key) + 1, val_off += strlen(val) + 1)
                    if (strlen(key) < 9 || strncasecmp(key, "profile\\", 8) != 0)
                        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got STATSUPDATE with suspicious key \"%s\" value \"%s\"", conn_get_socket(c), key, val);
                    else
                        account_set_strattr(account, key, val);
        }
    }

    return 0;
}

static int _client_playerinforeq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_playerinforeq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad PLAYERINFOREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_playerinforeq), packet_get_size(packet));
        return -1;
    }

    {
        char const *username;
        char const *info;
        t_account *account;

        if (!(username = packet_get_str_const(packet, sizeof(t_client_playerinforeq), USER_NAME_MAX))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad PLAYERINFOREQ (missing or too long username)", conn_get_socket(c));
            return -1;
        }
        if (!(info = packet_get_str_const(packet, sizeof(t_client_playerinforeq) + strlen(username) + 1, MAX_PLAYERINFO_STR))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad PLAYERINFOREQ (missing or too long info)", conn_get_socket(c));
            return -1;
        }

        if (info[0] != '\0')
            conn_set_playerinfo(c, info);
        if (!username[0])
            username = conn_get_loggeduser(c);

        account = conn_get_account(c);

        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;
        packet_set_size(rpacket, sizeof(t_server_playerinforeply));
        packet_set_type(rpacket, SERVER_PLAYERINFOREPLY);

        if (account) {
            packet_append_string(rpacket, username);
            packet_append_string(rpacket, conn_get_playerinfo(c));
            packet_append_string(rpacket, username);
        } else {
            packet_append_string(rpacket, "");
            packet_append_string(rpacket, "");
            packet_append_string(rpacket, "");
        }
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_progident2(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_progident2)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad PROGIDENT2 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_progident2), packet_get_size(packet));
        return -1;
    }

    /* d2 uses this packet with clienttag = 0 to request the channel list */
    if (bn_int_get(packet->u.client_progident2.clienttag)) {
        if (_check_allowed_client(bn_int_get(packet->u.client_progident2.clienttag))) {
            conn_set_state(c, conn_state_destroy);
            return 0;
        }
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] CLIENT_PROGIDENT2 clienttag=0x%08x", conn_get_socket(c), bn_int_get(packet->u.client_progident2.clienttag));

        /* Hmm... no archtag.  Hope we get it in CLIENT_AUTHREQ1 (but we won't if we use the shortcut) */

        conn_set_clienttag(c, bn_int_get(packet->u.client_progident2.clienttag));
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_channellist));
        packet_set_type(rpacket, SERVER_CHANNELLIST);
        {
            t_channel *ch;
            t_elem const *curr;
            int cnt= 0;

///            LIST_TRAVERSE_CONST(channellist(), curr) {
///                ch = (t_channel*)elem_get_data(curr);
			BEGIN(_ch,channellist())
			{
				ch=*_ch;
                if 
				(
					(!(channel_get_flags(ch) & channel_flags_clan)) 
					&& 
					(
						!prefs_get_hide_temp_channels() 
						|| 
						channel_get_permanent(ch) 
						||
						(
							(cnt<15)
							&&
							(
								0==strncasecmp(channel_get_name(ch),"ch-",3)
								||
								0==strncasecmp(channel_get_name(ch),"op ",3)
								||
								0==strncasecmp(channel_get_name(ch),"cl- ",3)
							)
						)
					)
					&& 
					(
						!channel_get_clienttag(ch) 
						|| 
						strcmp(channel_get_clienttag(ch), clienttag_uint_to_str(conn_get_clienttag(c))) == 0
					)
					&&
					(
						!(channel_get_flags(ch) & channel_flags_thevoid)) 	// don't display theVoid in channel list
						&&
                        (
							(channel_get_max(ch) != 0) 
							|| 
							(
								(channel_get_max(ch) == 0) 
								&& 
								(account_is_operator_or_admin(conn_get_account(c), channel_get_name(ch)))
							)
						)
					)	// don't display restricted channel for no admins/ops
                {
                    packet_append_string(rpacket, channel_get_name(ch));
                    if(!channel_get_permanent(ch))cnt++;
                }
            }
        }
        packet_append_string(rpacket, "");
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_joinchannel(t_connection * c, t_packet const *const packet)
{
    t_account *account;
    char const *cname;
    int found = 1;
    t_clan *user_clan;
    int clantag;
    t_uint32 clienttag;
    t_channel *channel;

    if (packet_get_size(packet) < sizeof(t_client_joinchannel)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad JOINCHANNEL packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_joinchannel), packet_get_size(packet));
        return -1;
    }

    account = conn_get_account(c);

    if (!(cname = packet_get_str_const(packet, sizeof(t_client_joinchannel), CHANNEL_NAME_LEN))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad JOINCHANNEL (missing or too long cname)", conn_get_socket(c));
        return -1;
    }

    if ((channel = conn_get_channel(c)) && (strcasecmp(channel_get_name(channel), cname) == 0))
        return 0;		//we are allready in this channel

    clienttag = conn_get_clienttag(c);
    if ((clienttag == CLIENTTAG_WARCRAFT3_UINT) || (clienttag == CLIENTTAG_WAR3XP_UINT)) {
        conn_update_w3_playerinfo(c);
        switch (bn_int_get(packet->u.client_joinchannel.channelflag)) {
        case CLIENT_JOINCHANNEL_NORMAL:
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] CLIENT_JOINCHANNEL_NORMAL channel \"%s\"", conn_get_socket(c), cname);

            if (prefs_get_ask_new_channel() && (!(channellist_find_channel_by_name(cname, conn_get_country(c), realm_get_name(conn_get_realm(c)))))) {
                found = 0;
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] didn't find channel \"%s\" to join", conn_get_socket(c), cname);
                message_send_text(c, message_type_channeldoesnotexist, c, cname);
            }
            break;
        case CLIENT_JOINCHANNEL_GENERIC:

            if ((user_clan = account_get_clan(account)) && (clantag = clan_get_clantag(user_clan)))
                sprintf((char *) cname, "Clan %c%c%c%c", (clantag >> 24), (clantag >> 16) & 0xff, (clantag >> 8) & 0xff, clantag & 0xff);
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] CLIENT_JOINCHANNEL_GENERIC channel \"%s\"", conn_get_socket(c), cname);

            /* don't have to do anything here */
            break;
        case CLIENT_JOINCHANNEL_CREATE:
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] CLIENT_JOINCHANNEL_CREATE channel \"%s\"", conn_get_socket(c), cname);
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] CLIENT_JOINCHANNEL_CREATE channel \"%s\"", conn_get_socket(c), cname);
            /* don't have to do anything here */
            break;
        }

        if (found && conn_set_channel(c, cname) < 0)
            conn_set_channel(c, CHANNEL_NAME_BANNED);	/* should not fail */
    } else {

        // not W3
        if (conn_set_channel(c, cname) < 0)
            conn_set_channel(c, CHANNEL_NAME_BANNED);	/* should not fail */
    }
    // here we set channel flags on user
    channel_set_userflags(c);

    return 0;
}

static int _client_message(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_message)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad MESSAGE packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_message), packet_get_size(packet));
        return -1;
    }

    {
        char const *text;
        t_channel const *channel;

        if (!(text = packet_get_str_const(packet, sizeof(t_client_message), MAX_MESSAGE_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad MESSAGE (missing or too long text)", conn_get_socket(c));
            return -1;
        }

        conn_set_idletime(c);

        if ((channel = conn_get_channel(c)))
            channel_message_log(channel, c, 1, text);
        /* we don't log game commands currently */



        if (text[0] == '/')
            handle_command(c, text);
        else if (channel && !conn_quota_exceeded(c, text))
            channel_message_send(channel, message_type_talk, c, text);
        /* else discard */
    }

    return 0;
}

struct glist_cbdata {
    unsigned tcount, counter;
    t_connection *c;
    t_game_type gtype;
    t_packet *rpacket;
};

static int _glist_cb(t_game * game, void *data)
{
    struct glist_cbdata *cbdata = (struct glist_cbdata*)data;
    char clienttag_str[5];
    t_server_gamelistreply_game glgame;
    unsigned int addr;
    unsigned short port;
    bn_int game_spacer = { 1, 0, 0, 0 };

    cbdata->tcount++;
    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] considering listing game=\"%s\", pass=\"%s\" clienttag=\"%s\" gtype=%d", conn_get_socket(cbdata->c), game_get_name(game), game_get_pass(game), tag_uint_to_str(clienttag_str, game_get_clienttag(game)), (int) game_get_type(game));

    if (prefs_get_hide_pass_games() && game_get_flag(game) == game_flag_private) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] not listing because game is passworded or has private flag", conn_get_socket(cbdata->c));
        return 0;
    }
    if (prefs_get_hide_started_games() && game_get_status(game) != game_status_open) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] not listing because game is not open", conn_get_socket(cbdata->c));
        return 0;
    }
    if (game_get_clienttag(game) != conn_get_clienttag(cbdata->c)) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] not listing because game is for a different client", conn_get_socket(cbdata->c));
        return 0;
    }
    if (cbdata->gtype != game_type_all && game_get_type(game) != cbdata->gtype) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] not listing because game is wrong type", conn_get_socket(cbdata->c));
        return 0;
    }
    if (conn_get_versioncheck(cbdata->c) &&
            conn_get_versioncheck(game_get_owner(game)) &&
            versioncheck_get_versiontag(conn_get_versioncheck(cbdata->c)) &&
            versioncheck_get_versiontag(conn_get_versioncheck(game_get_owner(game))) &&
            strcmp(versioncheck_get_versiontag(conn_get_versioncheck(cbdata->c)), versioncheck_get_versiontag(conn_get_versioncheck(game_get_owner(game)))) != 0) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] not listing because game is wrong versiontag", conn_get_socket(cbdata->c));
        return 0;
    }
    bn_short_set(&glgame.gametype, gtype_to_bngtype(game_get_type(game)));
    bn_short_set(&glgame.unknown1, SERVER_GAMELISTREPLY_GAME_UNKNOWN1);
    bn_short_set(&glgame.unknown3, SERVER_GAMELISTREPLY_GAME_UNKNOWN3);
    addr = game_get_addr(game);
    port = game_get_port(game);
    trans_net(conn_get_addr(cbdata->c), &addr, &port);
    bn_short_nset(&glgame.port, port);
    bn_int_nset(&glgame.game_ip, addr);
    bn_int_set(&glgame.unknown4, SERVER_GAMELISTREPLY_GAME_UNKNOWN4);
    bn_int_set(&glgame.unknown5, SERVER_GAMELISTREPLY_GAME_UNKNOWN5);
    switch (game_get_status(game)) {
    case game_status_started:
        bn_int_set(&glgame.status, SERVER_GAMELISTREPLY_GAME_STATUS_STARTED);
        break;
    case game_status_full:
        bn_int_set(&glgame.status, SERVER_GAMELISTREPLY_GAME_STATUS_FULL);
        break;
    case game_status_open:
        bn_int_set(&glgame.status, SERVER_GAMELISTREPLY_GAME_STATUS_OPEN);
        break;
    case game_status_done:
        bn_int_set(&glgame.status, SERVER_GAMELISTREPLY_GAME_STATUS_DONE);
        break;
    default:
        eventlog(eventlog_level_warn, __FUNCTION__, "[%d] game \"%s\" has bad status=%d", conn_get_socket(cbdata->c), game_get_name(game), (int) game_get_status(game));
        bn_int_set(&glgame.status, 0);
    }
    bn_int_set(&glgame.unknown6, SERVER_GAMELISTREPLY_GAME_UNKNOWN6);

    if (packet_get_size(cbdata->rpacket) + sizeof(glgame) + strlen(game_get_name(game)) + 1 + strlen(game_get_pass(game)) + 1 + strlen(game_get_info(game)) + 1 > MAX_PACKET_SIZE) {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] out of room for games", conn_get_socket(cbdata->c));
        return -1;			/* no more room */
    }

    if (cbdata->counter) {
        packet_append_data(cbdata->rpacket, &game_spacer, sizeof(game_spacer));
    }

    packet_append_data(cbdata->rpacket, &glgame, sizeof(glgame));
    packet_append_string(cbdata->rpacket, game_get_name(game));
    packet_append_string(cbdata->rpacket, game_get_pass(game));
    packet_append_string(cbdata->rpacket, game_get_info(game));
    cbdata->counter++;

    return 0;
}

static int _client_gamelistreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    char const *gamename;
    char const *gamepass;
    unsigned short bngtype;
    t_game_type gtype;
    t_clienttag clienttag;
    t_game *game;
    t_server_gamelistreply_game glgame;
    unsigned int addr;
    unsigned short port;
    char clienttag_str[5];

    if (packet_get_size(packet) < sizeof(t_client_gamelistreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad GAMELISTREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_gamelistreq), packet_get_size(packet));
        return -1;
    }

    if (!(gamename = packet_get_str_const(packet, sizeof(t_client_gamelistreq), GAME_NAME_LEN))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad GAMELISTREQ (missing or too long gamename)", conn_get_socket(c));
        return -1;
    }

    if (!(gamepass = packet_get_str_const(packet, sizeof(t_client_gamelistreq) + strlen(gamename) + 1, GAME_PASS_LEN))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad GAMELISTREQ (missing or too long password)", conn_get_socket(c));
        return -1;
    }

    bngtype = bn_short_get(packet->u.client_gamelistreq.gametype);
    clienttag = conn_get_clienttag(c);
    gtype = bngreqtype_to_gtype(clienttag, bngtype);
    if (!(rpacket = packet_create(packet_class_bnet)))
        return -1;
    packet_set_size(rpacket, sizeof(t_server_gamelistreply));
    packet_set_type(rpacket, SERVER_GAMELISTREPLY);

    bn_int_set(&rpacket->u.server_gamelistreply.sstatus, 0);

    /* specific game requested? */
    if (gamename[0] != '\0') {
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] GAMELISTREPLY looking for specific game tag=\"%s\" bngtype=0x%08x gtype=%d name=\"%s\" pass=\"%s\"", conn_get_socket(c), tag_uint_to_str(clienttag_str, clienttag), bngtype, (int) gtype, gamename, gamepass);
        if ((game = gamelist_find_game(gamename, clienttag, gtype))) {
            /* game found but first we need to make sure everything is OK */
            bn_int_set(&rpacket->u.server_gamelistreply.gamecount, 0);
            switch (game_get_status(game)) {
            case game_status_started:
                bn_int_set(&rpacket->u.server_gamelistreply.sstatus, SERVER_GAMELISTREPLY_GAME_SSTATUS_STARTED);
                eventlog(eventlog_level_debug, __FUNCTION__, "[%d] GAMELISTREPLY found but started", conn_get_socket(c));
                break;
            case game_status_full:
                bn_int_set(&rpacket->u.server_gamelistreply.sstatus, SERVER_GAMELISTREPLY_GAME_SSTATUS_FULL);
                eventlog(eventlog_level_debug, __FUNCTION__, "[%d] GAMELISTREPLY found but full", conn_get_socket(c));
                break;
            case game_status_done:
                bn_int_set(&rpacket->u.server_gamelistreply.sstatus, SERVER_GAMELISTREPLY_GAME_SSTATUS_NOTFOUND);
                eventlog(eventlog_level_debug, __FUNCTION__, "[%d] GAMELISTREPLY found but done", conn_get_socket(c));
                break;
            case game_status_open:
                if (strcmp(gamepass, game_get_pass(game))) {	/* passworded game must match password in request */
                    bn_int_set(&rpacket->u.server_gamelistreply.sstatus, SERVER_GAMELISTREPLY_GAME_SSTATUS_PASS);
                    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] GAMELISTREPLY found but is password protected and wrong password given", conn_get_socket(c));
                    break;
                }

                /* everything seems fine, lets reply with the found game */
                bn_int_set(&glgame.status, SERVER_GAMELISTREPLY_GAME_STATUS_OPEN);
                bn_short_set(&glgame.gametype, gtype_to_bngtype(game_get_type(game)));
                bn_short_set(&glgame.unknown1, SERVER_GAMELISTREPLY_GAME_UNKNOWN1);
                bn_short_set(&glgame.unknown3, SERVER_GAMELISTREPLY_GAME_UNKNOWN3);
                addr = game_get_addr(game);
                port = game_get_port(game);
                trans_net(conn_get_addr(c), &addr, &port);
                bn_short_nset(&glgame.port, port);
                bn_int_nset(&glgame.game_ip, addr);
                bn_int_set(&glgame.unknown4, SERVER_GAMELISTREPLY_GAME_UNKNOWN4);
                bn_int_set(&glgame.unknown5, SERVER_GAMELISTREPLY_GAME_UNKNOWN5);
                bn_int_set(&glgame.unknown6, SERVER_GAMELISTREPLY_GAME_UNKNOWN6);

                packet_append_data(rpacket, &glgame, sizeof(glgame));
                packet_append_string(rpacket, game_get_name(game));
                packet_append_string(rpacket, game_get_pass(game));
                packet_append_string(rpacket, game_get_info(game));
                bn_int_set(&rpacket->u.server_gamelistreply.gamecount, 1);
                eventlog(eventlog_level_debug, __FUNCTION__, "[%d] GAMELISTREPLY specific game found", conn_get_socket(c));
                break;
            default:
                eventlog(eventlog_level_warn, __FUNCTION__, "[%d] game \"%s\" has bad status %d", conn_get_socket(c), game_get_name(game), game_get_status(game));
            }
        } else {
            bn_int_set(&rpacket->u.server_gamelistreply.gamecount, 0);
            eventlog(eventlog_level_debug, __FUNCTION__, "[%d] GAMELISTREPLY specific game doesn't seem to exist", conn_get_socket(c));
        }
    } else {			/* list all public games of this type */
        struct glist_cbdata cbdata;

        if (gtype == game_type_all)
            eventlog(eventlog_level_debug, __FUNCTION__, "GAMELISTREPLY looking for public games tag=\"%s\" bngtype=0x%08x gtype=all", tag_uint_to_str(clienttag_str, clienttag), bngtype);
        else
            eventlog(eventlog_level_debug, __FUNCTION__, "GAMELISTREPLY looking for public games tag=\"%s\" bngtype=0x%08x gtype=%d", tag_uint_to_str(clienttag_str, clienttag), bngtype, (int) gtype);

        cbdata.counter = 0;
        cbdata.tcount = 0;
        cbdata.c = c;
        cbdata.gtype = gtype;
        cbdata.rpacket = rpacket;
        gamelist_traverse(_glist_cb,&cbdata);

        bn_int_set(&rpacket->u.server_gamelistreply.gamecount, cbdata.counter);
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] GAMELISTREPLY sent %u of %u games", conn_get_socket(c), cbdata.counter, cbdata.tcount);
    }

    conn_push_outqueue(c, rpacket);
    packet_del_ref(rpacket);

    return 0;
}

static int _client_joingame(t_connection * c, t_packet const *const packet)
{
    char const *gamename;
    char const *gamepass;
    t_game *game;
    t_game_type gtype;

    if (packet_get_size(packet) < sizeof(t_client_join_game)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad JOIN_GAME packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_join_game), packet_get_size(packet));
        return -1;
    }

    if (!(gamename = packet_get_str_const(packet, sizeof(t_client_join_game), GAME_NAME_LEN))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CLIENT_JOIN_GAME (missing or too long gamename)", conn_get_socket(c));
        return -1;
    }

    if (!(gamepass = packet_get_str_const(packet, sizeof(t_client_join_game) + strlen(gamename) + 1, GAME_PASS_LEN))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CLIENT_JOIN_GAME packet (missing or too long gamepass)", conn_get_socket(c));
        return -1;
    }

    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] trying to join game \"%s\" pass=\"%s\"", conn_get_socket(c), gamename, gamepass);

    if (conn_get_joingamewhisper_ack(c) == 0) {
        watchlist_notify_event(conn_get_account(c), gamename, conn_get_clienttag(c), watch_event_joingame);
        conn_set_joingamewhisper_ack(c, 1);	/* 1 = already whispered. We reset this each time user joins a channel */
        clanmember_on_change_status_by_connection(c);
    }

    if (conn_get_channel(c))
        conn_set_channel(c, NULL);

    if (!strcmp(gamename, "BNet") && !handle_anongame_join(c)) {
        gtype = game_type_anongame;
        gamename = NULL;
        return 0;		/* tmp: do not record any anongames as yet */
    } else {
        if (!(game = gamelist_find_game(gamename, conn_get_clienttag(c), game_type_all))) {
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] unable to find game \"%s\" for user to join", conn_get_socket(c), gamename);
            return 0;
        }
        gtype = game_get_type(game);
        gamename = game_get_name(game);
        if ((gtype == game_type_ladder && account_get_auth_joinladdergame(conn_get_account(c)) == 0) ||	/* default to true */
                (gtype != game_type_ladder && account_get_auth_joinnormalgame(conn_get_account(c)) == 0)) {	/* default to true */
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] game join for \"%s\" to \"%s\" refused (no authority)", conn_get_socket(c), conn_get_username(c), gamename);
            /* If the user is not in a game, then map authorization
               will fail and keep them from playing. */
            return 0;
        }
    }

    if (conn_set_game(c, gamename, gamepass, "", gtype, STARTVER_UNKNOWN) < 0)
        eventlog(eventlog_level_info, __FUNCTION__, "[%d] \"%s\" joined game \"%s\", but could not be recorded on server", conn_get_socket(c), conn_get_username(c), gamename);
    else
        eventlog(eventlog_level_info, __FUNCTION__, "[%d] \"%s\" joined game \"%s\"", conn_get_socket(c), conn_get_username(c), gamename);

    return 0;
}

static int _client_startgame1(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_startgame1)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME1 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_startgame1), packet_get_size(packet));
        return -1;
    }

    {
        char const *gamename;
        char const *gamepass;
        char const *gameinfo;
        unsigned short bngtype;
        unsigned int status;
        t_game *currgame;

        if (!(gamename = packet_get_str_const(packet, sizeof(t_client_startgame1), GAME_NAME_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME1 packet (missing or too long gamename)", conn_get_socket(c));
            return -1;
        }
        if (!(gamepass = packet_get_str_const(packet, sizeof(t_client_startgame1) + strlen(gamename) + 1, GAME_PASS_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME1 packet (missing or too long gamepass)", conn_get_socket(c));
            return -1;
        }
        if (!(gameinfo = packet_get_str_const(packet, sizeof(t_client_startgame1) + strlen(gamename) + 1 + strlen(gamepass) + 1, GAME_INFO_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME1 packet (missing or too long gameinfo)", conn_get_socket(c));
            return -1;
        }
        if (conn_get_joingamewhisper_ack(c) == 0) {
            if (watchlist_notify_event(conn_get_account(c), gamename, conn_get_clienttag(c), watch_event_joingame) == 0)
                eventlog(eventlog_level_info, "handle_bnet", "Told Mutual Friends your in game %s", gamename);

            conn_set_joingamewhisper_ack(c, 1);	//1 = already whispered. We reset this each time user joins a channel
        }


        bngtype = bn_short_get(packet->u.client_startgame1.gametype);
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] got startgame1 status for game \"%s\" is 0x%08x (gametype = 0x%04hx)", conn_get_socket(c), gamename, bn_int_get(packet->u.client_startgame1.status), bngtype);
        status = bn_int_get(packet->u.client_startgame1.status) & CLIENT_STARTGAME1_STATUSMASK;

        if ((currgame = conn_get_game(c))) {
            switch (status) {
            case CLIENT_STARTGAME1_STATUS_STARTED:
                game_set_status(currgame, game_status_started);
                break;
            case CLIENT_STARTGAME1_STATUS_FULL:
                game_set_status(currgame, game_status_full);
                break;
            case CLIENT_STARTGAME1_STATUS_OPEN:
                game_set_status(currgame, game_status_open);
                break;
            case CLIENT_STARTGAME1_STATUS_DONE:
                game_set_status(currgame, game_status_done);
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] game \"%s\" is finished", conn_get_socket(c), gamename);
                break;
            }
        } else if (status != CLIENT_STARTGAME1_STATUS_DONE) {
            t_game_type gtype;

            gtype = bngtype_to_gtype(conn_get_clienttag(c), bngtype);
            if ((gtype == game_type_ladder && account_get_auth_createladdergame(conn_get_account(c)) == 0) ||	/* default to true */
                    (gtype != game_type_ladder && account_get_auth_createnormalgame(conn_get_account(c)) == 0))	/* default to true */
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] game start for \"%s\" refused (no authority)", conn_get_socket(c), conn_get_username(c));
            else
                conn_set_game(c, gamename, gamepass, gameinfo, gtype, STARTVER_GW1);

            if ((rpacket = packet_create(packet_class_bnet))) {
                packet_set_size(rpacket, sizeof(t_server_startgame1_ack));
                packet_set_type(rpacket, SERVER_STARTGAME1_ACK);

                if (conn_get_game(c))
                    bn_int_set(&rpacket->u.server_startgame1_ack.reply, SERVER_STARTGAME1_ACK_OK);
                else
                    bn_int_set(&rpacket->u.server_startgame1_ack.reply, SERVER_STARTGAME1_ACK_NO);

                conn_push_outqueue(c, rpacket);
                packet_del_ref(rpacket);
            }
        } else
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] client tried to set game status DONE to destroyed game", conn_get_socket(c));
    }

    return 0;
}

static int _client_startgame3(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_startgame3)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME3 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_startgame3), packet_get_size(packet));
        return -1;
    }

    {
        char const *gamename;
        char const *gamepass;
        char const *gameinfo;
        unsigned short bngtype;
        unsigned int status;
        t_game *currgame;

        if (!(gamename = packet_get_str_const(packet, sizeof(t_client_startgame3), GAME_NAME_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME3 packet (missing or too long gamename)", conn_get_socket(c));
            return -1;
        }
        if (!(gamepass = packet_get_str_const(packet, sizeof(t_client_startgame3) + strlen(gamename) + 1, GAME_PASS_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME3 packet (missing or too long gamepass)", conn_get_socket(c));
            return -1;
        }
        if (!(gameinfo = packet_get_str_const(packet, sizeof(t_client_startgame3) + strlen(gamename) + 1 + strlen(gamepass) + 1, GAME_INFO_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME3 packet (missing or too long gameinfo)", conn_get_socket(c));
            return -1;
        }
        if (conn_get_joingamewhisper_ack(c) == 0) {
            if (watchlist_notify_event(conn_get_account(c), gamename, conn_get_clienttag(c), watch_event_joingame) == 0)
                eventlog(eventlog_level_info, "handle_bnet", "Told Mutual Friends your in game %s", gamename);

            conn_set_joingamewhisper_ack(c, 1);	//1 = already whispered. We reset this each time user joins a channel
        }
        bngtype = bn_short_get(packet->u.client_startgame3.gametype);
        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] got startgame3 status for game \"%s\" is 0x%08x (gametype = 0x%04hx)", conn_get_socket(c), gamename, bn_int_get(packet->u.client_startgame3.status), bngtype);
        status = bn_int_get(packet->u.client_startgame3.status) & CLIENT_STARTGAME3_STATUSMASK;

        if ((currgame = conn_get_game(c))) {
            switch (status) {
            case CLIENT_STARTGAME3_STATUS_STARTED:
                game_set_status(currgame, game_status_started);
                break;
            case CLIENT_STARTGAME3_STATUS_FULL:
                game_set_status(currgame, game_status_full);
                break;
            case CLIENT_STARTGAME3_STATUS_OPEN1:
            case CLIENT_STARTGAME3_STATUS_OPEN:
                game_set_status(currgame, game_status_open);
                break;
            case CLIENT_STARTGAME3_STATUS_DONE:
                game_set_status(currgame, game_status_done);
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] game \"%s\" is finished", conn_get_socket(c), gamename);
                break;
            }
        } else if (status != CLIENT_STARTGAME3_STATUS_DONE) {
            t_game_type gtype;

            gtype = bngtype_to_gtype(conn_get_clienttag(c), bngtype);
            if ((gtype == game_type_ladder && account_get_auth_createladdergame(conn_get_account(c)) == 0) || (gtype != game_type_ladder && account_get_auth_createnormalgame(conn_get_account(c)) == 0))
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] game start for \"%s\" refused (no authority)", conn_get_socket(c), conn_get_username(c));
            else
                conn_set_game(c, gamename, gamepass, gameinfo, gtype, STARTVER_GW3);

            if ((rpacket = packet_create(packet_class_bnet))) {
                packet_set_size(rpacket, sizeof(t_server_startgame3_ack));
                packet_set_type(rpacket, SERVER_STARTGAME3_ACK);

                if (conn_get_game(c))
                    bn_int_set(&rpacket->u.server_startgame3_ack.reply, SERVER_STARTGAME3_ACK_OK);
                else
                    bn_int_set(&rpacket->u.server_startgame3_ack.reply, SERVER_STARTGAME3_ACK_NO);

                conn_push_outqueue(c, rpacket);
                packet_del_ref(rpacket);
            }
        } else
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] client tried to set game status DONE to destroyed game", conn_get_socket(c));
    }

    return 0;
}

static int _client_startgame4(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_startgame4)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME4 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_startgame4), packet_get_size(packet));
        return -1;
    }
    // Quick hack to make W3 part channels when creating a game
    if (conn_get_channel(c))
        conn_set_channel(c, NULL);

    {
        char const *gamename;
        char const *gamepass;
        char const *gameinfo;
        unsigned short bngtype;
        unsigned int status;
        unsigned int flag;
        unsigned short option;
        t_game *currgame;

        if (!(gamename = packet_get_str_const(packet, sizeof(t_client_startgame4), GAME_NAME_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME4 packet (missing or too long gamename)", conn_get_socket(c));
            return -1;
        }
        if (!(gamepass = packet_get_str_const(packet, sizeof(t_client_startgame4) + strlen(gamename) + 1, GAME_PASS_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME4 packet (missing or too long gamepass)", conn_get_socket(c));
            return -1;
        }
        if (!(gameinfo = packet_get_str_const(packet, sizeof(t_client_startgame4) + strlen(gamename) + 1 + strlen(gamepass) + 1, GAME_INFO_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad STARTGAME4 packet (missing or too long gameinfo)", conn_get_socket(c));
            return -1;
        }
        if (conn_get_joingamewhisper_ack(c) == 0) {
            if (watchlist_notify_event(conn_get_account(c), gamename, conn_get_clienttag(c), watch_event_joingame) == 0)
                eventlog(eventlog_level_info, "handle_bnet", "Told Mutual Friends your in game %s", gamename);

            conn_set_joingamewhisper_ack(c, 1);	//1 = already whispered. We reset this each time user joins a channel
        }
        bngtype = bn_short_get(packet->u.client_startgame4.gametype);
        option = bn_short_get(packet->u.client_startgame4.option);
        status = bn_int_get(packet->u.client_startgame4.status);
        flag = bn_short_get(packet->u.client_startgame4.flag);

        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] got startgame4 status for game \"%s\" is 0x%08x (gametype=0x%04hx option=0x%04hx, flag=0x%04hx)", conn_get_socket(c), gamename, status, bngtype, option, flag);

        if ((currgame = conn_get_game(c))) {
            if ((status & CLIENT_STARTGAME4_STATUSMASK_OPEN_VALID) == status) {
                if (status & CLIENT_STARTGAME4_STATUS_START)
                    game_set_status(currgame, game_status_started);
                else if (status & CLIENT_STARTGAME4_STATUS_FULL)
                    game_set_status(currgame, game_status_full);
                else
                    game_set_status(currgame, game_status_open);
            } else {
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] unknown startgame4 status %d (clienttag: %s)", conn_get_socket(c), status, clienttag_uint_to_str(conn_get_clienttag(c)));
            }
        } else if ((status & CLIENT_STARTGAME4_STATUSMASK_INIT_VALID) == status) {
            /*valid creation status would be:
               0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13, 0x80, 0x81, 0x82, 0x83 */

            t_game_type gtype;

            gtype = bngtype_to_gtype(conn_get_clienttag(c), bngtype);
            if ((gtype == game_type_ladder && account_get_auth_createladdergame(conn_get_account(c)) == 0) || (gtype != game_type_ladder && account_get_auth_createnormalgame(conn_get_account(c)) == 0))
                eventlog(eventlog_level_info, __FUNCTION__, "[%d] game start for \"%s\" refused (no authority)", conn_get_socket(c), conn_get_username(c));
            else if (conn_set_game(c, gamename, gamepass, gameinfo, gtype, STARTVER_GW4) == 0) {
                game_set_option(conn_get_game(c), bngoption_to_goption(conn_get_clienttag(c), gtype, option));
                if (status & CLIENT_STARTGAME4_STATUS_PRIVATE)
                    game_set_flag(conn_get_game(c), game_flag_private);
                if (status & CLIENT_STARTGAME4_STATUS_FULL)
                    game_set_status(conn_get_game(c), game_status_full);
                //FIXME: still need special handling for status disc-is-loss and replay
            }
        } else
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] client tried to set game status 0x%x to unexistent game (clienttag: %s)", conn_get_socket(c), status, clienttag_uint_to_str(conn_get_clienttag(c)));
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_startgame4_ack));
        packet_set_type(rpacket, SERVER_STARTGAME4_ACK);

        if (conn_get_game(c))
            bn_int_set(&rpacket->u.server_startgame4_ack.reply, SERVER_STARTGAME4_ACK_OK);
        else
            bn_int_set(&rpacket->u.server_startgame4_ack.reply, SERVER_STARTGAME4_ACK_NO);
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    /* First, send an ECHO_REQ */
    if ((rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_echoreq));
        packet_set_type(rpacket, SERVER_ECHOREQ);
        bn_int_set(&rpacket->u.server_echoreq.ticks, get_ticks());
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_closegame(t_connection * c, t_packet const *const packet)
{
    t_game *game;

    eventlog(eventlog_level_info, __FUNCTION__, "[%d] client closing game", conn_get_socket(c));
    if (packet_get_type(packet) == CLIENT_CLOSEGAME2 || ((conn_get_clienttag(c) != CLIENTTAG_WARCRAFT3_UINT) && (conn_get_clienttag(c) != CLIENTTAG_WAR3XP_UINT)))
        conn_set_game(c, NULL, NULL, NULL, game_type_none, 0);
    else if ((game = conn_get_game(c)))
        game_set_status(game, game_status_started);

    return 0;
}

static int _client_gamereport(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_game_report)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad GAME_REPORT packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_game_report), packet_get_size(packet));
        return -1;
    }

    {
        t_account *my_account;
        t_account *other_account;
        t_game *game;
        unsigned int player_count;
        unsigned int i,s;
        t_client_game_report_result const *result_data;
        unsigned int result_off;
        t_game_result result;
        char const *player;
        unsigned int player_off;
        t_game_result *results;

        player_count = bn_int_get(packet->u.client_gamerep.count);

        if (!(game = conn_get_game(c))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got GAME_REPORT when not in a game for user \"%s\"", conn_get_socket(c), conn_get_username(c));
            return -1;
        }

        eventlog(eventlog_level_info, __FUNCTION__, "[%d] CLIENT_GAME_REPORT: %s (%u players)", conn_get_socket(c), conn_get_username(c), player_count);
        my_account = conn_get_account(c);

        results = (t_game_result*)xmalloc(sizeof(t_game_result) * game_get_count(game));

        for (i = 0; i < game_get_count(game); i++)
            results[i] = game_result_none;

        for (i = 0, result_off = sizeof(t_client_game_report), player_off = sizeof(t_client_game_report) + player_count * sizeof(t_client_game_report_result); i < player_count; i++, result_off += sizeof(t_client_game_report_result), player_off += strlen(player) + 1) {
            if (!(result_data = (const t_client_game_report_result*)packet_get_data_const(packet, result_off, sizeof(t_client_game_report_result)))) {
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] got corrupt GAME_REPORT packet (missing results %u-%u)", conn_get_socket(c), i + 1, player_count);
                break;
            }
            if (!(player = packet_get_str_const(packet, player_off, USER_NAME_MAX))) {
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] got corrupt GAME_REPORT packet (missing players %u-%u)", conn_get_socket(c), i + 1, player_count);
                break;
            }

            if (player[0] == '\0')	/* empty slots have empty player name */
                continue;

            if (i >= game_get_count(game)) {
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] got more results than the game had players - ignoring extra results", conn_get_socket(c));
                break;
            }

            /*result = bngresult_to_gresult(bn_int_get(result_data->result));
            results[i] = result;

            eventlog(eventlog_level_debug, __FUNCTION__, "[%d] got player %d (\"%s\") result %s", conn_get_socket(c), i, player, game_result_get_str(result));*/

            if (!(other_account = accountlist_find_account(player))) {
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] got GAME_REPORT with unknown player \"%s\"", conn_get_socket(c), player);
                break;
            }
            // as player position in game structure and in game report might differ,
            // search for right position
            for (s=0; s<game_get_count(game); s++)
            {
                if (game_get_player(game,s)==other_account) break;
            }

            if (s<game_get_count(game))
            {
                result = bngresult_to_gresult(bn_int_get(result_data->result));
                results[s] = result;
                eventlog(eventlog_level_debug, __FUNCTION__, "[%d] got player %d (\"%s\") result %s", conn_get_socket(c), i, player, game_result_get_str(result));
            }
            else
            {
                eventlog(eventlog_level_error,__FUNCTION__,"[%d] got GAME_REPORT for non-participating player \"%s\"",conn_get_socket(c),player);
            }


        }

        if (i == player_count) {	/* if everything checked out... */
            char const *head;
            char const *body;

            if (!(head = packet_get_str_const(packet, player_off, MAX_GAMEREP_HEAD_STR)))
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] got GAME_REPORT with missing or too long report head", conn_get_socket(c));
            else {
                player_off += strlen(head) + 1;
                if (!(body = packet_get_str_const(packet, player_off, MAX_GAMEREP_BODY_STR)))
                    eventlog(eventlog_level_error, __FUNCTION__, "[%d] got GAME_REPORT with missing or too ling report body", conn_get_socket(c));
                else
                    game_set_report(game, my_account, head, body);
            }
        }

        if (game_set_reported_results(game, my_account, results) < 0)
            xfree((void *) results);

        eventlog(eventlog_level_debug, __FUNCTION__, "[%d] finished parsing result... now leaving game", conn_get_socket(c));
        conn_set_game(c, NULL, NULL, NULL, game_type_none, 0);
    }

    return 0;
}

static int _client_leavechannel(t_connection * c, t_packet const *const packet)
{
    /* If this user in a channel, notify everyone that the user has left */
    if (conn_get_channel(c))
        conn_set_channel(c, NULL);
    return 0;
}

static int _client_ladderreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;


    if (packet_get_size(packet) < sizeof(t_client_ladderreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad LADDERREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_ladderreq), packet_get_size(packet));
        return -1;
    }

    {
        t_ladder_entry entry;
        unsigned int i;
        unsigned int type;
        unsigned int start;
        unsigned int count;
        unsigned int idnum;
        t_account *account;
        t_clienttag clienttag;
        char const *timestr;
        t_bnettime bt;
        t_ladder_id id;

        clienttag = conn_get_clienttag(c);

        type = bn_int_get(packet->u.client_ladderreq.type);
        start = bn_int_get(packet->u.client_ladderreq.startplace);
        count = bn_int_get(packet->u.client_ladderreq.count);
        idnum = bn_int_get(packet->u.client_ladderreq.id);

        /* eventlog(eventlog_level_debug,__FUNCTION__,"got LADDERREQ type=%u start=%u count=%u id=%u",type,start,count,id); */

        switch (idnum) {
        case CLIENT_LADDERREQ_ID_STANDARD:
            id = ladder_id_normal;
            break;
        case CLIENT_LADDERREQ_ID_IRONMAN:
            id = ladder_id_ironman;
            break;
        default:
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got unknown ladder ladderreq.id=0x%08x", conn_get_socket(c), idnum);
            id = ladder_id_normal;
        }

        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;
        packet_set_size(rpacket, sizeof(t_server_ladderreply));
        packet_set_type(rpacket, SERVER_LADDERREPLY);

        bn_int_set(&rpacket->u.server_ladderreply.clienttag, clienttag);
        bn_int_set(&rpacket->u.server_ladderreply.id, idnum);
        bn_int_set(&rpacket->u.server_ladderreply.type, type);
        bn_int_set(&rpacket->u.server_ladderreply.startplace, start);
        bn_int_set(&rpacket->u.server_ladderreply.count, count);

        for (i = start; i < start + count; i++) {
            switch (type) {
            case CLIENT_LADDERREQ_TYPE_HIGHESTRATED:
                if (!(account = ladder_get_account_by_rank(i + 1, ladder_sort_highestrated, ladder_time_active, clienttag, id)))
                    account = ladder_get_account_by_rank(i + 1, ladder_sort_highestrated, ladder_time_current, clienttag, id);
                break;
            case CLIENT_LADDERREQ_TYPE_MOSTWINS:
                if (!(account = ladder_get_account_by_rank(i + 1, ladder_sort_mostwins, ladder_time_active, clienttag, id)))
                    account = ladder_get_account_by_rank(i + 1, ladder_sort_mostwins, ladder_time_current, clienttag, id);
                break;
            case CLIENT_LADDERREQ_TYPE_MOSTGAMES:
                if (!(account = ladder_get_account_by_rank(i + 1, ladder_sort_mostgames, ladder_time_active, clienttag, id)))
                    account = ladder_get_account_by_rank(i + 1, ladder_sort_mostgames, ladder_time_current, clienttag, id);
                break;
            default:
                account = NULL;
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] got unknown value for ladderreq.type=%u", conn_get_socket(c), type);
            }

            if (account) {
                bn_int_set(&entry.active.wins, account_get_ladder_active_wins(account, clienttag, id));
                bn_int_set(&entry.active.loss, account_get_ladder_active_losses(account, clienttag, id));
                bn_int_set(&entry.active.disconnect, account_get_ladder_active_disconnects(account, clienttag, id));
                bn_int_set(&entry.active.rating, account_get_ladder_active_rating(account, clienttag, id));
                bn_int_set(&entry.active.rank, account_get_ladder_active_rank(account,clienttag,id)-1);
                if (!(timestr = account_get_ladder_active_last_time(account, clienttag, id)))
                    timestr = BNETD_LADDER_DEFAULT_TIME;
                bnettime_set_str(&bt, timestr);
                bnettime_to_bn_long(bt, &entry.lastgame_active);

                bn_int_set(&entry.current.wins, account_get_ladder_wins(account, clienttag, id));
                bn_int_set(&entry.current.loss, account_get_ladder_losses(account, clienttag, id));
                bn_int_set(&entry.current.disconnect, account_get_ladder_disconnects(account, clienttag, id));
                bn_int_set(&entry.current.rating, account_get_ladder_rating(account, clienttag, id));
                bn_int_set(&entry.current.rank, account_get_ladder_rank(account,clienttag,id)-1);
                if (!(timestr = account_get_ladder_last_time(account, clienttag, id)))
                    timestr = BNETD_LADDER_DEFAULT_TIME;
                bnettime_set_str(&bt, timestr);
                bnettime_to_bn_long(bt, &entry.lastgame_current);
            } else {
                bn_int_set(&entry.active.wins, 0);
                bn_int_set(&entry.active.loss, 0);
                bn_int_set(&entry.active.disconnect, 0);
                bn_int_set(&entry.active.rating, 0);
                bn_int_set(&entry.active.rank, 0);
                bn_long_set_a_b(&entry.lastgame_active, 0, 0);

                bn_int_set(&entry.current.wins, 0);
                bn_int_set(&entry.current.loss, 0);
                bn_int_set(&entry.current.disconnect, 0);
                bn_int_set(&entry.current.rating, 0);
                bn_int_set(&entry.current.rank, 0);
                bn_long_set_a_b(&entry.lastgame_current, 0, 0);
            }

            bn_int_set(&entry.ttest[0], i);	// rank
            bn_int_set(&entry.ttest[1], 0);	//
            bn_int_set(&entry.ttest[2], 0);	//
            if (account)
                bn_int_set(&entry.ttest[3], account_get_ladder_high_rating(account, clienttag, id));
            else
                bn_int_set(&entry.ttest[3], 0);
            bn_int_set(&entry.ttest[4], 0);	//
            bn_int_set(&entry.ttest[5], 0);	//

            packet_append_data(rpacket, &entry, sizeof(entry));

            if (account)
                packet_append_string(rpacket, account_get_name(account));
            else
                packet_append_string(rpacket, " ");	/* use a space so the client won't show the user's own account when double-clicked on */
        }

        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_laddersearchreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_laddersearchreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad LADDERSEARCHREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_laddersearchreq), packet_get_size(packet));
        return -1;
    }

    {
        char const *playername;
        t_account *account;
        unsigned int idnum;
        unsigned int rank;	/* starts at zero */
        t_ladder_id id;
        t_clienttag ctag = conn_get_clienttag(c);

        idnum = bn_int_get(packet->u.client_laddersearchreq.id);

        switch (idnum) {
        case CLIENT_LADDERREQ_ID_STANDARD:
            id = ladder_id_normal;
            break;
        case CLIENT_LADDERREQ_ID_IRONMAN:
            id = ladder_id_ironman;
            break;
        default:
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got unknown ladder laddersearchreq.id=0x%08x", conn_get_socket(c), idnum);
            id = ladder_id_normal;
        }

        if (!(playername = packet_get_str_const(packet, sizeof(t_client_laddersearchreq), USER_NAME_MAX))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad LADDERSEARCHREQ packet (missing or too long playername)", conn_get_socket(c));
            return -1;
        }

        if (!(account = accountlist_find_account(playername)))
            rank = SERVER_LADDERSEARCHREPLY_RANK_NONE;
        else {
            switch (bn_int_get(packet->u.client_laddersearchreq.type)) {
            case CLIENT_LADDERSEARCHREQ_TYPE_HIGHESTRATED:
                if (!(rank=ladder_get_rank_by_account(account, ladder_sort_highestrated, ladder_time_active, ctag, id)))
                {
                    if (!(rank = ladder_get_rank_by_account(account, ladder_sort_highestrated,
                                                            ladder_time_current, ctag, id)) ||
                            (ladder_get_account_by_rank(rank, ladder_sort_highestrated, ladder_time_active, ctag, id)))
                        rank = 0;
                }
                break;
            case CLIENT_LADDERSEARCHREQ_TYPE_MOSTWINS:
                if (!(rank=ladder_get_rank_by_account(account, ladder_sort_mostwins, ladder_time_active, ctag, id)))
                {
                    if (!(rank = ladder_get_rank_by_account(account, ladder_sort_mostwins,
                                                            ladder_time_current, ctag, id)) ||
                            (ladder_get_account_by_rank(rank, ladder_sort_mostwins, ladder_time_active, ctag, id)))
                        rank = 0;
                }
                break;
            case CLIENT_LADDERSEARCHREQ_TYPE_MOSTGAMES:
                if (!(rank=ladder_get_rank_by_account(account, ladder_sort_mostgames, ladder_time_active, ctag, id)))
                {
                    if (!(rank = ladder_get_rank_by_account(account, ladder_sort_mostgames,
                                                            ladder_time_current, ctag, id)) ||
                            (ladder_get_account_by_rank(rank, ladder_sort_mostgames, ladder_time_active, ctag, id)))
                        rank = 0;
                }
                break;
            default:
                rank = 0;
                eventlog(eventlog_level_error, __FUNCTION__, "[%d] got unknown ladder search type %u", conn_get_socket(c), bn_int_get(packet->u.client_laddersearchreq.type));
            }

            if (rank == 0)
                rank = SERVER_LADDERSEARCHREPLY_RANK_NONE;
            else
                rank--;
        }

        if (!(rpacket = packet_create(packet_class_bnet)))
            return -1;
        packet_set_size(rpacket, sizeof(t_server_laddersearchreply));
        packet_set_type(rpacket, SERVER_LADDERSEARCHREPLY);
        bn_int_set(&rpacket->u.server_laddersearchreply.rank, rank);
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_mapauthreq1(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_mapauthreq1)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad MAPAUTHREQ1 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_mapauthreq1), packet_get_size(packet));
        return -1;
    }

    {
        char const *mapname;
        t_game *game;

        if (!(mapname = packet_get_str_const(packet, sizeof(t_client_mapauthreq1), MAP_NAME_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad MAPAUTHREQ1 packet (missing or too long mapname)", conn_get_socket(c));
            return -1;
        }

        game = conn_get_game(c);

        if (game) {
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] map auth requested for map \"%s\" gametype \"%s\"", conn_get_socket(c), mapname, game_type_get_str(game_get_type(game)));
            game_set_mapname(game, mapname);
        } else
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] map auth requested when not in a game", conn_get_socket(c));

        if ((rpacket = packet_create(packet_class_bnet))) {
            unsigned int val;

            if (!game) {
                val = SERVER_MAPAUTHREPLY1_NO;
                eventlog(eventlog_level_debug, __FUNCTION__, "[%d] map authorization denied (not in a game)", conn_get_socket(c));
            } else if (strcasecmp(game_get_mapname(game), mapname) != 0) {
                val = SERVER_MAPAUTHREPLY1_NO;
                eventlog(eventlog_level_debug, __FUNCTION__, "[%d] map authorization denied (map name \"%s\" does not match game map name \"%s\")", conn_get_socket(c), mapname, game_get_mapname(game));
            } else {
                game_set_status(game, game_status_started);

                if (game_get_type(game) == game_type_ladder) {
                    val = SERVER_MAPAUTHREPLY1_LADDER_OK;
                    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] giving map ladder authorization (in a ladder game)", conn_get_socket(c));
                } else if (ladder_check_map(game_get_mapname(game), game_get_maptype(game), conn_get_clienttag(c))) {
                    val = SERVER_MAPAUTHREPLY1_LADDER_OK;
                    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] giving map ladder authorization (is a ladder map)", conn_get_socket(c));
                } else {
                    val = SERVER_MAPAUTHREPLY1_LADDER_OK;
                    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] giving map normal authorization", conn_get_socket(c));
                }
            }

            packet_set_size(rpacket, sizeof(t_server_mapauthreply1));
            packet_set_type(rpacket, SERVER_MAPAUTHREPLY1);
            bn_int_set(&rpacket->u.server_mapauthreply1.response, val);
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_mapauthreq2(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_mapauthreq2)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad MAPAUTHREQ2 packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_mapauthreq2), packet_get_size(packet));
        return -1;
    }

    {
        char const *mapname;
        t_game *game;

        if (!(mapname = packet_get_str_const(packet, sizeof(t_client_mapauthreq2), MAP_NAME_LEN))) {
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad MAPAUTHREQ2 packet (missing or too long mapname)", conn_get_socket(c));
            return -1;
        }

        game = conn_get_game(c);

        if (game) {
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] map auth requested for map \"%s\" gametype \"%s\"", conn_get_socket(c), mapname, game_type_get_str(game_get_type(game)));
            game_set_mapname(game, mapname);
        } else
            eventlog(eventlog_level_info, __FUNCTION__, "[%d] map auth requested when not in a game", conn_get_socket(c));

        if ((rpacket = packet_create(packet_class_bnet))) {
            unsigned int val;

            if (!game) {
                val = SERVER_MAPAUTHREPLY2_NO;
                eventlog(eventlog_level_debug, __FUNCTION__, "[%d] map authorization denied (not in a game)", conn_get_socket(c));
            } else if (strcasecmp(game_get_mapname(game), mapname) != 0) {
                val = SERVER_MAPAUTHREPLY2_NO;
                eventlog(eventlog_level_debug, __FUNCTION__, "[%d] map authorization denied (map name \"%s\" does not match game map name \"%s\")", conn_get_socket(c), mapname, game_get_mapname(game));
            } else {
                game_set_status(game, game_status_started);

                if (game_get_type(game) == game_type_ladder) {
                    val = SERVER_MAPAUTHREPLY2_LADDER_OK;
                    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] giving map ladder authorization (in a ladder game)", conn_get_socket(c));
                } else if (ladder_check_map(game_get_mapname(game), game_get_maptype(game), conn_get_clienttag(c))) {
                    val = SERVER_MAPAUTHREPLY2_LADDER_OK;
                    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] giving map ladder authorization (is a ladder map)", conn_get_socket(c));
                } else {
                    val = SERVER_MAPAUTHREPLY2_LADDER_OK;
                    eventlog(eventlog_level_debug, __FUNCTION__, "[%d] giving map normal authorization", conn_get_socket(c));
                }
            }

            packet_set_size(rpacket, sizeof(t_server_mapauthreply2));
            packet_set_type(rpacket, SERVER_MAPAUTHREPLY2);
            bn_int_set(&rpacket->u.server_mapauthreply2.response, val);
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_changeclient(t_connection * c, t_packet const *const packet)
{
    t_versioncheck *vc;

    if (packet_get_size(packet) < sizeof(t_client_changeclient)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad CLIENT_CHANGECLIENT packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_changeclient), packet_get_size(packet));
        return -1;
    }

    if (_check_allowed_client(bn_int_get(packet->u.client_changeclient.clienttag))) {
        conn_set_state(c, conn_state_destroy);
        return 0;
    }

    conn_set_clienttag(c, bn_int_get(packet->u.client_changeclient.clienttag));

    vc = conn_get_versioncheck(c);
    versioncheck_set_versiontag(vc, clienttag_uint_to_str(conn_get_clienttag(c)));

    if (vc && versioncheck_get_versiontag(vc)) {
        switch (versioncheck_validate(vc, conn_get_archtag(c), conn_get_clienttag(c), conn_get_clientexe(c), conn_get_versionid(c), conn_get_gameversion(c), conn_get_checksum(c))) {
        case -1:		/* failed test... client has been modified */
        case 0:		/* not listed in table... can't tell if client has been modified */
            eventlog(eventlog_level_error, __FUNCTION__, "[%d] error revalidating, allowing anyway", conn_get_socket(c));
            break;
        }

        eventlog(eventlog_level_info, __FUNCTION__, "[%d] client versiontag set to \"%s\"", conn_get_socket(c), versioncheck_get_versiontag(vc));
    }

    return 0;
}

static int _client_w3xp_clanmemberlistreq(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_w3xp_clanmemberlist_req)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLANMEMBERLIST_REQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clanmemberlist_req), packet_get_size(packet));
        return -1;
    }

    clan_send_memberlist(c, packet);
    return 0;
}

static int _client_w3xp_clan_motdreq(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_w3xp_clan_motdreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLAN_MOTDREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clan_motdreq), packet_get_size(packet));
        return -1;
    }

    clan_send_motd_reply(c, packet);
    return 0;
}

static int _client_w3xp_clan_motdchg(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_w3xp_clan_motdreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLAN_MOTDCHGREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clan_motdreq), packet_get_size(packet));
        return -1;
    }

    clan_save_motd_chg(c, packet);
    return 0;
}

static int _client_w3xp_clan_disbandreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_w3xp_clan_disbandreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLAN_DISBANDREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clan_disbandreq), packet_get_size(packet));
        return -1;
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        t_clan *clan;
        t_account *myacc;
        myacc = conn_get_account(c);
        clan = account_get_clan(myacc);
        packet_set_size(rpacket, sizeof(t_server_w3xp_clan_disbandreply));
        packet_set_type(rpacket, SERVER_W3XP_CLAN_DISBANDREPLY);
        bn_int_set(&rpacket->u.server_w3xp_clan_disbandreply.count, bn_int_get(packet->u.client_w3xp_clan_disbandreq.count));
        if ((clanlist_remove_clan(clan) == 0) && (clan_remove(clan_get_clantag(clan)) == 0)) {
            bn_byte_set(&rpacket->u.server_w3xp_clan_disbandreply.result, SERVER_W3XP_CLAN_DISBANDREPLY_RESULT_OK);
            clan_close_status_window_on_disband(clan);
            clan_send_packet_to_online_members(clan, rpacket);
            packet_del_ref(rpacket);
            clan_destroy(clan);
        } else {
            bn_byte_set(&rpacket->u.server_w3xp_clan_disbandreply.result, SERVER_W3XP_CLAN_DISBANDREPLY_RESULT_EXCEPTION);
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_w3xp_clan_createreq(t_connection * c, t_packet const *const packet)
{
    if (packet_get_size(packet) < sizeof(t_client_w3xp_clan_createreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLAN_INFOREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clan_createreq), packet_get_size(packet));
        return -1;
    }

    clan_get_possible_member(c, packet);

    return 0;
}

static int _client_w3xp_clan_createinvitereq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    int size;

    if ((size = packet_get_size(packet)) < sizeof(t_client_w3xp_clan_createinvitereq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLAN_CREATEINVITEREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clan_createinvitereq), packet_get_size(packet));
        return -1;
    }

    if ((rpacket = packet_create(packet_class_bnet))) {
        const char *clanname;
        const char *username;
        int clantag;
        int offset = sizeof(t_client_w3xp_clan_createinvitereq);
        t_clan *clan;
        clanname = packet_get_str_const(packet, offset, CLAN_NAME_MAX);
        offset += (strlen(clanname) + 1);
        clantag = *((int *) packet_get_data_const(packet, offset, 4));
        offset += 4;
        if ((clan = clan_create(conn_get_account(c), clantag, clanname, NULL)) && clanlist_add_clan(clan)) {
            char membercount = *((char *) packet_get_data_const(packet, offset, 1));
            clan_set_created(clan, -membercount);
            packet_set_size(rpacket, sizeof(t_server_w3xp_clan_createinvitereq));
            packet_set_type(rpacket, SERVER_W3XP_CLAN_CREATEINVITEREQ);
            bn_int_set(&rpacket->u.server_w3xp_clan_createinvitereq.count, bn_int_get(packet->u.client_w3xp_clan_createinvitereq.count));
            bn_int_set(&rpacket->u.server_w3xp_clan_createinvitereq.clantag, clantag);
            packet_append_string(rpacket, clanname);
            packet_append_string(rpacket, conn_get_username(c));
            packet_append_data(rpacket, packet_get_data_const(packet, offset, size - offset), size - offset);
            offset++;
            do {
                username = packet_get_str_const(packet, offset, USER_NAME_MAX);
                if (username) {
                    t_connection *conn;
                    offset += (strlen(username) + 1);
                    if ((conn = connlist_find_connection_by_accountname(username)) != NULL) {
                        if (prefs_get_clan_newer_time() > 0)
                            clan_add_member(clan, conn_get_account(conn), CLAN_NEW);
                        else
                            clan_add_member(clan, conn_get_account(conn), CLAN_PEON);
                        conn_push_outqueue(conn, rpacket);
                    }
                }
            } while (username && (offset < size));
        } else {
            packet_set_size(rpacket, sizeof(t_server_w3xp_clan_createinvitereply));
            packet_set_type(rpacket, SERVER_W3XP_CLAN_CREATEINVITEREPLY);
            bn_int_set(&rpacket->u.server_w3xp_clan_createinvitereply.count, bn_int_get(packet->u.client_w3xp_clan_createinvitereply.count));
            bn_byte_set(&rpacket->u.server_w3xp_clan_createinvitereply.status, 0);
        }
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_w3xp_clan_createinvitereply(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    t_connection *conn;
    t_clan *clan;
    const char *username;
    int offset;
    char status;

    if (packet_get_size(packet) < sizeof(t_client_w3xp_clan_createinvitereply)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLAN_CREATEINVITEREPLY packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clan_createinvitereq), packet_get_size(packet));
        return -1;
    }
    offset = sizeof(t_client_w3xp_clan_createinvitereply);
    username = packet_get_str_const(packet, offset, USER_NAME_MAX);
    offset += (strlen(username) + 1);
    status = *((char *) packet_get_data_const(packet, offset, 1));
    if ((conn = connlist_find_connection_by_accountname(username)) == NULL)
        return -1;
    if ((clan = account_get_creating_clan(conn_get_account(conn))) == NULL)
        return -1;
    if ((status != W3XP_CLAN_INVITEREPLY_ACCEPT) && (rpacket = packet_create(packet_class_bnet))) {
        packet_set_size(rpacket, sizeof(t_server_w3xp_clan_createinvitereply));
        packet_set_type(rpacket, SERVER_W3XP_CLAN_CREATEINVITEREPLY);
        bn_int_set(&rpacket->u.server_w3xp_clan_createinvitereply.count, bn_int_get(packet->u.client_w3xp_clan_createinvitereply.count));
        bn_byte_set(&rpacket->u.server_w3xp_clan_createinvitereply.status, status);
        packet_append_string(rpacket, conn_get_username(c));
        conn_push_outqueue(conn, rpacket);
        packet_del_ref(rpacket);
        if (clan) {
            clanlist_remove_clan(clan);
            clan_destroy(clan);
        }
    } else {
        int created = clan_get_created(clan);
        if (created > 0) {
            eventlog(eventlog_level_error, __FUNCTION__, "clan %s has already been created", clan_get_name(clan));
            return 0;
        }
        created++;
        if ((created >= 0) && (rpacket = packet_create(packet_class_bnet))) {
            clan_set_created(clan, 1);
            clan_set_creation_time(clan, time(NULL));
            packet_set_size(rpacket, sizeof(t_server_w3xp_clan_createinvitereply));
            packet_set_type(rpacket, SERVER_W3XP_CLAN_CREATEINVITEREPLY);
            bn_int_set(&rpacket->u.server_w3xp_clan_createinvitereply.count, bn_int_get(packet->u.client_w3xp_clan_createinvitereply.count));
            bn_byte_set(&rpacket->u.server_w3xp_clan_createinvitereply.status, 0);
            packet_append_string(rpacket, "");
            conn_push_outqueue(conn, rpacket);
            packet_del_ref(rpacket);
            clan_send_status_window_on_create(clan);
            clan_save(clan);
        } else
            clan_set_created(clan, created);
    }
    return 0;
}

static int _client_w3xp_clanmember_rankupdatereq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_w3xp_clanmember_rankupdate_req)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLANMEMBER_RANKUPDATE_REQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clanmember_rankupdate_req), packet_get_size(packet));
        return -1;
    }

    if ((rpacket = packet_create(packet_class_bnet)) != NULL) {
        int offset = sizeof(t_client_w3xp_clanmember_rankupdate_req);
        const char *username;
        char status;
        t_clan *clan;
        t_clanmember *dest_member;

        packet_set_size(rpacket, sizeof(t_server_w3xp_clanmember_rankupdate_reply));
        packet_set_type(rpacket, SERVER_W3XP_CLANMEMBER_RANKUPDATE_REPLY);
        bn_int_set(&rpacket->u.server_w3xp_clanmember_rankupdate_reply.count,
                   bn_int_get(packet->u.client_w3xp_clanmember_rankupdate_req.count));
        username = packet_get_str_const(packet, offset, USER_NAME_MAX);
        offset += (strlen(username) + 1);
        status = *((char *) packet_get_data_const(packet, offset, 1));

        clan = account_get_clan(conn_get_account(c));
        dest_member = clan_find_member_by_name(clan, username);
        if (clanmember_set_status(dest_member, status) == 0)
        {
            bn_byte_set(&rpacket->u.server_w3xp_clanmember_rankupdate_reply.result,
                        SERVER_W3XP_CLANMEMBER_RANKUPDATE_SUCCESS);
            clanmember_on_change_status(dest_member);
        }
        else
        {
            bn_byte_set(&rpacket->u.server_w3xp_clanmember_rankupdate_reply.result,
                        SERVER_W3XP_CLANMEMBER_RANKUPDATE_FAILED);
        }
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_w3xp_clanmember_removereq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_w3xp_clanmember_remove_req)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLANMEMBER_REMOVE_REQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clanmember_remove_req), packet_get_size(packet));
        return -1;
    }

    if ((rpacket = packet_create(packet_class_bnet)) != NULL) {
        t_account *acc;
        t_clan *clan;
        const char *username;
        t_clanmember *member;
        t_connection *dest_conn;
        packet_set_size(rpacket, sizeof(t_server_w3xp_clanmember_remove_reply));
        packet_set_type(rpacket, SERVER_W3XP_CLANMEMBER_REMOVE_REPLY);
        bn_int_set(&rpacket->u.server_w3xp_clanmember_remove_reply.count,
                   bn_int_get(packet->u.client_w3xp_clanmember_remove_req.count));
        username = packet_get_str_const(packet, sizeof(t_client_w3xp_clanmember_remove_req), USER_NAME_MAX);
        bn_byte_set(&rpacket->u.server_w3xp_clanmember_remove_reply.result,
                    SERVER_W3XP_CLANMEMBER_REMOVE_FAILED); // initially presume it failed

        if ((acc = conn_get_account(c)) && (clan = account_get_clan(acc)) && (member = clan_find_member_by_name(clan, username))) {
            dest_conn = clanmember_get_conn(member);
            if (clan_remove_member(clan, member) == 0) {
                t_packet *rpacket2;
                if (dest_conn) {
                    clan_close_status_window(dest_conn);
                    conn_update_w3_playerinfo(dest_conn);
                    channel_rejoin(dest_conn);
                }
                if ((rpacket2 = packet_create(packet_class_bnet)) != NULL) {
                    packet_set_size(rpacket2, sizeof(t_server_w3xp_clanmember_removed_notify));
                    packet_set_type(rpacket2, SERVER_W3XP_CLANMEMBER_REMOVED_NOTIFY);
                    packet_append_string(rpacket2, username);
                    clan_send_packet_to_online_members(clan, rpacket2);
                    packet_del_ref(rpacket2);
                }
                bn_byte_set(&rpacket->u.server_w3xp_clanmember_remove_reply.result,
                            SERVER_W3XP_CLANMEMBER_REMOVE_SUCCESS);
            }
        }
        conn_push_outqueue(c, rpacket);
        packet_del_ref(rpacket);
    }

    return 0;
}

static int _client_w3xp_clan_membernewchiefreq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;

    if (packet_get_size(packet) < sizeof(t_client_w3xp_clan_membernewchiefreq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLAN_MEMBERNEWCHIEFREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clan_createreq), packet_get_size(packet));
        return -1;
    }

    if ((rpacket = packet_create(packet_class_bnet)) != NULL) {
        t_account *acc;
        t_clan *clan;
        t_clanmember *oldmember;
        t_clanmember *newmember;
        const char *username;
        packet_set_size(rpacket, sizeof(t_server_w3xp_clan_membernewchiefreply));
        packet_set_type(rpacket, SERVER_W3XP_CLAN_MEMBERNEWCHIEFREPLY);
        bn_int_set(&rpacket->u.server_w3xp_clan_membernewchiefreply.count, bn_int_get(packet->u.client_w3xp_clan_membernewchiefreq.count));
        username = packet_get_str_const(packet, sizeof(t_client_w3xp_clan_membernewchiefreq), USER_NAME_MAX);
        if ((acc = conn_get_account(c)) && (oldmember = account_get_clanmember(acc)) && (clanmember_get_status(oldmember) == CLAN_CHIEFTAIN) && (clan = clanmember_get_clan(oldmember)) && (newmember = clan_find_member_by_name(clan, username)) && (clanmember_set_status(oldmember, CLAN_GRUNT) == 0) && (clanmember_set_status(newmember, CLAN_CHIEFTAIN) == 0)) {
            clanmember_on_change_status(oldmember);
            clanmember_on_change_status(newmember);
            bn_byte_set(&rpacket->u.server_w3xp_clan_membernewchiefreply.result, SERVER_W3XP_CLAN_MEMBERNEWCHIEFREPLY_SUCCESS);
            clan_send_packet_to_online_members(clan, rpacket);
            packet_del_ref(rpacket);
        } else {
            bn_byte_set(&rpacket->u.server_w3xp_clan_membernewchiefreply.result, SERVER_W3XP_CLAN_MEMBERNEWCHIEFREPLY_FAILED);
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_w3xp_clan_invitereq(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    t_clan *clan;
    int clantag;
    const char *username;
    t_connection *conn;

    if (packet_get_size(packet) < sizeof(t_client_w3xp_clan_invitereq)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLAN_INVITEREQ packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clan_createreq), packet_get_size(packet));
        return -1;
    }

    if ((clan = account_get_clan(conn_get_account(c))) != NULL) {
        if (clan_get_member_count(clan) < prefs_get_clan_max_members()) {
            if ((clantag = clan_get_clantag(clan)) && (username = packet_get_str_const(packet, sizeof(t_client_w3xp_clan_invitereq), USER_NAME_MAX)) && (conn = connlist_find_connection_by_accountname(username)) && (rpacket = packet_create(packet_class_bnet))) {
                packet_set_size(rpacket, sizeof(t_server_w3xp_clan_invitereq));
                packet_set_type(rpacket, SERVER_W3XP_CLAN_INVITEREQ);
                bn_int_set(&rpacket->u.server_w3xp_clan_invitereq.count, bn_int_get(packet->u.client_w3xp_clan_invitereq.count));
                bn_int_set(&rpacket->u.server_w3xp_clan_invitereq.clantag, clantag);
                packet_append_string(rpacket, clan_get_name(clan));
                packet_append_string(rpacket, conn_get_username(c));
                conn_push_outqueue(conn, rpacket);
                packet_del_ref(rpacket);
            }
        } else if ((rpacket = packet_create(packet_class_bnet)) != NULL) {
            packet_set_size(rpacket, sizeof(t_server_w3xp_clan_invitereply));
            packet_set_type(rpacket, SERVER_W3XP_CLAN_INVITEREPLY);
            bn_int_set(&rpacket->u.server_w3xp_clan_invitereply.count, bn_int_get(packet->u.client_w3xp_clan_invitereq.count));
            bn_byte_set(&rpacket->u.server_w3xp_clan_invitereply.result, W3XP_CLAN_INVITEREPLY_CLANFULL);
            conn_push_outqueue(c, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_w3xp_clan_invitereply(t_connection * c, t_packet const *const packet)
{
    t_packet *rpacket;
    t_clan *clan;
    const char *username;
    t_connection *conn;
    int offset;
    char status;

    if (packet_get_size(packet) < sizeof(t_client_w3xp_clan_invitereply)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad W3XP_CLAN_INVITEREPLY packet (expected %u bytes, got %u)", conn_get_socket(c), sizeof(t_client_w3xp_clan_createreq), packet_get_size(packet));
        return -1;
    }

    offset = sizeof(t_client_w3xp_clan_invitereply);
    username = packet_get_str_const(packet, offset, USER_NAME_MAX);
    offset += (strlen(username) + 1);
    status = *((char *) packet_get_data_const(packet, offset, 1));
    if ((conn = connlist_find_connection_by_accountname(username)) != NULL) {
        if ((status == W3XP_CLAN_INVITEREPLY_ACCEPT) && (clan = account_get_clan(conn_get_account(conn)))) {
            char channelname[10];
            int clantag;
            if (clan_get_member_count(clan) < prefs_get_clan_max_members()) {
                t_clanmember *member = clan_add_member(clan, conn_get_account(c), 1);
                if ((member != NULL) && (clantag = clan_get_clantag(clan))) {
                    sprintf(channelname, "Clan %c%c%c%c", (clantag >> 24), (clantag >> 16) & 0xff, (clantag >> 8) & 0xff, clantag & 0xff);
                    if (conn_get_channel(c)) {
                        conn_update_w3_playerinfo(c);
                        channel_set_userflags(c);
                        if (conn_set_channel(c, channelname) < 0)
                            conn_set_channel(c, CHANNEL_NAME_BANNED);	/* should not fail */
                        clanmember_set_online(c);
                    }
                    clan_send_status_window(c);
                }
                if ((rpacket = packet_create(packet_class_bnet)) != NULL) {
                    packet_set_size(rpacket, sizeof(t_server_w3xp_clan_invitereply));
                    packet_set_type(rpacket, SERVER_W3XP_CLAN_INVITEREPLY);
                    bn_int_set(&rpacket->u.server_w3xp_clan_invitereply.count, bn_int_get(packet->u.client_w3xp_clan_invitereply.count));
                    bn_byte_set(&rpacket->u.server_w3xp_clan_invitereply.result, W3XP_CLAN_INVITEREPLY_SUCCESS);
                    conn_push_outqueue(conn, rpacket);
                    packet_del_ref(rpacket);
                }
            } else if ((rpacket = packet_create(packet_class_bnet)) != NULL) {
                packet_set_size(rpacket, sizeof(t_server_w3xp_clan_invitereply));
                packet_set_type(rpacket, SERVER_W3XP_CLAN_INVITEREPLY);
                bn_int_set(&rpacket->u.server_w3xp_clan_invitereply.count, bn_int_get(packet->u.client_w3xp_clan_invitereply.count));
                bn_byte_set(&rpacket->u.server_w3xp_clan_invitereply.result, W3XP_CLAN_INVITEREPLY_CLANFULL);
                conn_push_outqueue(conn, rpacket);
                packet_del_ref(rpacket);
            }
        } else if ((rpacket = packet_create(packet_class_bnet)) != NULL) {
            packet_set_size(rpacket, sizeof(t_server_w3xp_clan_invitereply));
            packet_set_type(rpacket, SERVER_W3XP_CLAN_INVITEREPLY);
            bn_int_set(&rpacket->u.server_w3xp_clan_invitereply.count, bn_int_get(packet->u.client_w3xp_clan_invitereply.count));
            bn_byte_set(&rpacket->u.server_w3xp_clan_invitereply.result, status);
            conn_push_outqueue(conn, rpacket);
            packet_del_ref(rpacket);
        }
    }

    return 0;
}

static int _client_crashdump(t_connection * c, t_packet const *const packet)
{
    return 0;
}

static int _client_setemailreply(t_connection * c, t_packet const *const packet)
{
    char const *email;
    t_account *account;

    if (!(email = packet_get_str_const(packet, sizeof(t_client_setemailreply), MAX_EMAIL_STR))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad SETEMAILREPLY packet", conn_get_socket(c));
        return -1;
    }
    if (!(account = conn_get_account(c))) {
        eventlog(eventlog_level_error, __FUNCTION__, "got NULL account for connection in setemail request");
        return -1;
    }
    if (account_get_email(account)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] account \"%s\" already have email set, ignore set email", conn_get_socket(c), account_get_name(account));
        return 0;
    }
    if (account_set_email(account, email) < 0) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] failed to init account \"%s\" email to \"%s\"", conn_get_socket(c), account_get_name(account), email);
        return 0;
    } else
        eventlog(eventlog_level_info, __FUNCTION__, "[%d] init account \"%s\" email to \"%s\"", conn_get_socket(c), account_get_name(account), email);
    return 0;
}

static int _client_changeemailreq(t_connection * c, t_packet const *const packet)
{
    char const *old;
    char const *new_;
    char const *username;
    char const *email;
    t_account *account;
    int pos;

    pos = sizeof(t_client_changeemailreq);
    if (!(username = packet_get_str_const(packet, pos, USER_NAME_MAX))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad username in CHANGEEMAILREQ packet", conn_get_socket(c));
        return -1;
    }
    pos += (strlen(username) + 1);
    if (!(old = packet_get_str_const(packet, pos, MAX_EMAIL_STR))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad old email in CHANGEEMAILREQ packet", conn_get_socket(c));
        return -1;
    }
    pos += (strlen(old) + 1);
    if (!(new_ = packet_get_str_const(packet, pos, MAX_EMAIL_STR))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad new_ email in CHANGEEMAILREQ packet", conn_get_socket(c));
        return -1;
    }
    if (!(account = accountlist_find_account(username))) {
        eventlog(eventlog_level_info, __FUNCTION__, "[%d] change email for \"%s\" refused (no such account)", conn_get_socket(c), username);
        return 0;
    }
    if (!(email = account_get_email(account)) || !email[0]) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] account \"%s\" do not have email set, ignore changing", conn_get_socket(c), account_get_name(account));
        return 0;
    }
    if (strcasecmp(email, old)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] account \"%s\" email mismatch, ignore changing", conn_get_socket(c), account_get_name(account));
        return 0;
    }
    if (account_set_email(account, new_) < 0) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] failed to change account \"%s\" email to \"%s\"", conn_get_socket(c), account_get_name(account), new_);
        return 0;
    } else
        eventlog(eventlog_level_info, __FUNCTION__, "[%d] change account \"%s\" email to \"%s\"", conn_get_socket(c), account_get_name(account), new_);
    return 0;
}

static int _client_getpasswordreq(t_connection * c, t_packet const *const packet)
{
    char const *username;
    char const *try_email;
    char const *email;
    t_account *account;
    int pos;

    pos = sizeof(t_client_getpasswordreq);
    if (!(username = packet_get_str_const(packet, pos, USER_NAME_MAX))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad username in GETPASSWORDREQ packet", conn_get_socket(c));
        return -1;
    }
    pos += (strlen(username) + 1);
    if (!(try_email = packet_get_str_const(packet, pos, MAX_EMAIL_STR))) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] got bad email in GETPASSWORDREQ packet", conn_get_socket(c));
        return -1;
    }
    if (!(account = accountlist_find_account(username))) {
        eventlog(eventlog_level_info, __FUNCTION__, "[%d] get password for \"%s\" refused (no such account)", conn_get_socket(c), username);
        return 0;
    }
    if (!(email = account_get_email(account)) || !email[0]) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] account \"%s\" do not have email set, ignore get password", conn_get_socket(c), account_get_name(account));
        return 0;
    }
    if (strcasecmp(email, try_email)) {
        eventlog(eventlog_level_error, __FUNCTION__, "[%d] account \"%s\" email mismatch, ignore get password", conn_get_socket(c), account_get_name(account));
        return 0;
    }
    /* TODO: send mail to user with the real password or changed password!?
     * (as we cannot get the real password back, we should only change the password)     --Soar */
    eventlog(eventlog_level_info, __FUNCTION__, "[%d] get password for account \"%s\" to email \"%s\"", conn_get_socket(c), account_get_name(account), email);
    return 0;
}
